Skip to content

Commit

Permalink
Be sure to properly treat both rvalues and lvalues in c2py functions
Browse files Browse the repository at this point in the history
  • Loading branch information
Wentzell committed Jun 26, 2020
1 parent 7d5195d commit 721fbc7
Show file tree
Hide file tree
Showing 11 changed files with 102 additions and 56 deletions.
19 changes: 15 additions & 4 deletions c++/cpp2py/converters/map.hpp
Original file line number Diff line number Diff line change
@@ -1,17 +1,28 @@
#pragma once
#include <map>
#include "../traits.hpp"

namespace cpp2py {

template <typename K, typename V> struct py_converter<std::map<K, V>> {

static PyObject *c2py(std::map<K, V> const &m) {
template <typename M> static PyObject *c2py(M &&m) {
static_assert(is_instantiation_of_v<std::map, std::decay_t<M>>, "Logic Error");

PyObject *d = PyDict_New();
for (auto &x : m) {
pyref k = py_converter<K>::c2py(x.first);
for (auto &[key, val] : m) {
pyref k, v;
if constexpr (std::is_reference_v<M>) {
k = convert_to_python(key);
v = convert_to_python(val);
} else { // Map passed as rvalue
k = convert_to_python(std::move(key));
v = convert_to_python(std::move(val));
}

// if the K is a list, we transform into a tuple
if (PyList_Check(k)) k = PyList_AsTuple(k);
pyref v = py_converter<V>::c2py(x.second);

if (k.is_null() or v.is_null() or (PyDict_SetItem(d, k, v) == -1)) {
Py_DECREF(d);
return NULL;
Expand Down
13 changes: 8 additions & 5 deletions c++/cpp2py/converters/optional.hpp
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
#pragma once
#include "../traits.hpp"

namespace cpp2py {

template <typename T> struct py_converter<std::optional<T>> {

using conv = py_converter<T>;
using conv = py_converter<std::decay_t<T>>;

static PyObject *c2py(std::optional<T> &op) {
if (!bool(op)) Py_RETURN_NONE;
return conv::c2py(*op);
}
template <typename O> static PyObject *c2py(O &&op) {
static_assert(is_instantiation_of_v<std::optional, std::decay_t<O>>, "Logic Error");
if (!bool(op)) Py_RETURN_NONE;
return conv::c2py(*(std::forward<O>(op)));
}

static bool is_convertible(PyObject *ob, bool raise_exception) {
return ((ob == Py_None) or conv::is_convertible(ob, raise_exception));
Expand Down
10 changes: 6 additions & 4 deletions c++/cpp2py/converters/pair.hpp
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
#pragma once
//#include <utility>
#include "../traits.hpp"

namespace cpp2py {

template <typename T1, typename T2> struct py_converter<std::pair<T1, T2>> {

static PyObject *c2py(std::pair<T1, T2> const &p) {
pyref x1 = py_converter<T1>::c2py(std::get<0>(p));
pyref x2 = py_converter<T2>::c2py(std::get<1>(p));
template <typename P> static PyObject *c2py(P &&p) {
static_assert(is_instantiation_of_v<std::pair, std::decay_t<P>>, "Logic error");
pyref x1 = convert_to_python(std::get<0>(std::forward<P>(p)));
pyref x2 = convert_to_python(std::get<1>(std::forward<P>(p)));

if (x1.is_null() or x2.is_null()) return NULL;
return PyTuple_Pack(2, (PyObject *)x1, (PyObject *)x2);
}
Expand Down
13 changes: 10 additions & 3 deletions c++/cpp2py/converters/set.hpp
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
#pragma once
#include <set>
#include "../traits.hpp"

namespace cpp2py {

template <typename K> struct py_converter<std::set<K>> {

static PyObject *c2py(std::set<K> const &s) {
template <typename S> static PyObject *c2py(S &&s) {
static_assert(is_instantiation_of_v<std::set, std::decay_t<S>>, "Logic error");
PyObject *set = PySet_New(NULL);
for (auto &x : s) {
pyref y = py_converter<K>::c2py(x);
pyref y;
if constexpr (std::is_reference_v<S>) {
y = convert_to_python(x);
} else { // s passed as rvalue
y = convert_to_python(std::move(x));
}
if (y.is_null() or (PySet_Add(set, y) == -1)) {
Py_DECREF(set);
return NULL;
Expand All @@ -31,7 +38,7 @@ namespace cpp2py {
if (raise_exception) { PyErr_SetString(PyExc_TypeError, "Cannot convert to std::set"); }
return false;
}

static std::set<K> py2c(PyObject *ob) {
std::set<K> res;
pyref keys_it = PyObject_GetIter(ob);
Expand Down
25 changes: 14 additions & 11 deletions c++/cpp2py/converters/tuple.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
#include <tuple>
#include <numeric>

#include "../traits.hpp"

namespace cpp2py {

template <typename... Types> struct py_converter<std::tuple<Types...>> {
Expand All @@ -11,28 +13,29 @@ namespace cpp2py {
using tuple_t = std::tuple<Types...>;

// c2py implementation
template <std::size_t... Is> static PyObject *c2py_impl(tuple_t const &t, std::index_sequence<Is...>) {
auto objs = std::array<pyref, sizeof...(Is)>{pyref(py_converter<Types>::c2py(std::get<Is>(t)))...};
template <typename T, auto... Is> static PyObject *c2py_impl(T &&t, std::index_sequence<Is...>) {
auto objs = std::array<pyref, sizeof...(Is)>{convert_to_python(std::get<Is>(std::forward<T>(t)))...};
bool one_is_null = std::accumulate(std::begin(objs), std::end(objs), false, [](bool x, PyObject *a) { return x or (a == NULL); });
if (one_is_null) return NULL;
return PyTuple_Pack(sizeof...(Types), (PyObject *)(objs[Is])...);
}

// is_convertible implementation
template <int N, typename T, typename... Tail> static bool is_convertible_impl(PyObject *seq, bool raise_exception) {
return py_converter<T>::is_convertible(PySequence_Fast_GET_ITEM(seq, N), raise_exception)
&& is_convertible_impl<N + 1, Tail...>(seq, raise_exception);
template <auto... Is> static bool is_convertible_impl(PyObject *seq, bool raise_exception, std::index_sequence<Is...>) {
return (py_converter<std::decay_t<Types>>::is_convertible(PySequence_Fast_GET_ITEM(seq, Is), raise_exception) and ...);
}
template <int> static bool is_convertible_impl(PyObject *seq, bool raise_exception) { return true; }

template <size_t... Is> static auto py2c_impl(std::index_sequence<Is...>, PyObject *seq) {
return std::make_tuple(py_converter<Types>::py2c(PySequence_Fast_GET_ITEM(seq, Is))...);
template <auto... Is> static auto py2c_impl(PyObject *seq, std::index_sequence<Is...>) {
return std::make_tuple(py_converter<std::decay_t<Types>>::py2c(PySequence_Fast_GET_ITEM(seq, Is))...);
}

public:
// -----------------------------------------

static PyObject *c2py(tuple_t const &t) { return c2py_impl(t, std::make_index_sequence<sizeof...(Types)>()); }
template <typename T> static PyObject *c2py(T &&t) {
static_assert(is_instantiation_of_v<std::tuple, std::decay_t<T>>, "Logic Error");
return c2py_impl(std::forward<T>(t), std::make_index_sequence<sizeof...(Types)>());
}

// -----------------------------------------

Expand All @@ -41,7 +44,7 @@ namespace cpp2py {
pyref seq = PySequence_Fast(ob, "expected a sequence");
// Sizes must match! Could we relax this condition to '<'?
if (PySequence_Fast_GET_SIZE((PyObject *)seq) != std::tuple_size<tuple_t>::value) goto _false;
if (!is_convertible_impl<0, Types...>((PyObject *)seq, raise_exception)) goto _false;
if (!is_convertible_impl((PyObject *)seq, raise_exception, std::make_index_sequence<sizeof...(Types)>())) goto _false;
return true;
}
_false:
Expand All @@ -53,7 +56,7 @@ namespace cpp2py {

static tuple_t py2c(PyObject *ob) {
pyref seq = PySequence_Fast(ob, "expected a sequence");
return py2c_impl(std::make_index_sequence<sizeof...(Types)>(), (PyObject *)seq);
return py2c_impl((PyObject *)seq, std::make_index_sequence<sizeof...(Types)>());
}
};
} // namespace cpp2py
13 changes: 7 additions & 6 deletions c++/cpp2py/converters/variant.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
#include <tuple>
#include <variant>

#include "../traits.hpp"

namespace cpp2py {

// std::variant<T...> converter
Expand Down Expand Up @@ -45,18 +47,17 @@ namespace cpp2py {
}

struct _visitor {
template <typename U> PyObject *operator()(U const &x) { return py_converter<U>::c2py(x); }
template <typename U> PyObject *operator()(U &&x) { return convert_to_python(std::forward<U>(x)); }
};

public:
static PyObject *c2py(std::variant<T...> const &v) {
//auto l = [](auto const &x) -> PyObject * { return py_converter<decltype(x)>::c2py(x); };
return visit(_visitor{}, v);
//return visit(_visitor{}, v);
template <typename V> static PyObject *c2py(V &&v) {
static_assert(is_instantiation_of_v<std::variant, std::decay_t<V>>);
return visit(_visitor{}, std::forward<V>(v));
}

static bool is_convertible(PyObject *ob, bool raise_exception) {
if ((... or py_converter<T>::is_convertible(ob, false))) return true;
if ((... or py_converter<std::decay_t<T>>::is_convertible(ob, false))) return true;
if (raise_exception) { PyErr_SetString(PyExc_TypeError, "Cannot convert to std::variant"); }
return false;
}
Expand Down
47 changes: 27 additions & 20 deletions c++/cpp2py/converters/vector.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,24 @@

namespace cpp2py {

template <typename T> static void delete_pycapsule(PyObject *capsule) {
auto *ptr = static_cast<std::unique_ptr<T[]> *>(PyCapsule_GetPointer(capsule, "guard"));
delete ptr;
}

// Convert vector to numpy_proxy, WARNING: Deep Copy
template <typename T> numpy_proxy make_numpy_proxy_from_vector(std::vector<T> const &v) {
template <typename V> numpy_proxy make_numpy_proxy_from_vector(V &&v) {
static_assert(is_instantiation_of_v<std::vector, std::decay_t<V>>, "Logic error");
using value_type = typename std::remove_reference_t<V>::value_type;

auto *data_ptr = new std::unique_ptr<T[]>{new T[v.size()]};
std::copy(begin(v), end(v), data_ptr->get());
auto capsule = PyCapsule_New(data_ptr, "guard", &delete_pycapsule<T>);
auto *vec_heap = new std::vector<value_type>{std::forward<V>(v)};
auto delete_pycapsule = [](PyObject *capsule) {
auto *ptr = static_cast<std::vector<value_type> *>(PyCapsule_GetPointer(capsule, "guard"));
delete ptr;
};
PyObject *capsule = PyCapsule_New(vec_heap, "guard", delete_pycapsule);

return {1, // rank
npy_type<std::remove_const_t<T>>,
(void *)data_ptr->get(),
std::is_const_v<T>,
v_t{static_cast<long>(v.size())}, // extents
v_t{sizeof(T)}, // strides
npy_type<value_type>,
(void *)vec_heap->data(),
std::is_const_v<value_type>,
std::vector<long>{long(vec_heap->size())}, // extents
std::vector<long>{sizeof(value_type)}, // strides
capsule};
}

Expand All @@ -50,14 +50,21 @@ namespace cpp2py {

template <typename T> struct py_converter<std::vector<T>> {

static PyObject *c2py(std::vector<T> const &v) {
template <typename V> static PyObject *c2py(V &&v) {
static_assert(is_instantiation_of_v<std::vector, std::decay_t<V>>, "Logic error");
using value_type = typename std::remove_reference_t<V>::value_type;

if constexpr (has_npy_type<T>) {
return make_numpy_proxy_from_vector(v).to_python();
if constexpr (has_npy_type<value_type>) {
return make_numpy_proxy_from_vector(std::forward<V>(v)).to_python();
} else { // Convert to Python List
PyObject *list = PyList_New(0);
for (auto const &x : v) {
pyref y = py_converter<T>::c2py(x);
for (auto &x : v) {
pyref y;
if constexpr(std::is_reference_v<V>){
y = py_converter<value_type>::c2py(x);
} else { // Vector passed as rvalue
y = py_converter<value_type>::c2py(std::move(x));
}
if (y.is_null() or (PyList_Append(list, y) == -1)) {
Py_DECREF(list);
return NULL;
Expand Down Expand Up @@ -122,7 +129,7 @@ namespace cpp2py {
std::vector<T> res;
pyref seq = PySequence_Fast(ob, "expected a sequence");
int len = PySequence_Size(ob);
for (int i = 0; i < len; i++) res.push_back(py_converter<T>::py2c(PySequence_Fast_GET_ITEM((PyObject *)seq, i))); //borrowed ref
for (int i = 0; i < len; i++) res.push_back(py_converter<std::decay_t<T>>::py2c(PySequence_Fast_GET_ITEM((PyObject *)seq, i))); //borrowed ref
return res;
}
};
Expand Down
4 changes: 1 addition & 3 deletions c++/cpp2py/numpy_proxy.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,13 @@

namespace cpp2py {

using v_t = std::vector<long>;

// the basic information for a numpy array
struct numpy_proxy {
int rank = 0;
long element_type = 0;
void *data = nullptr;
bool is_const = false;
v_t extents, strides;
std::vector<long> extents, strides;
PyObject *base = nullptr; // The ref. counting guard typically

// Returns a new ref (or NULL if failure) with a new numpy.
Expand Down
2 changes: 2 additions & 0 deletions c++/cpp2py/py_converter.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ namespace cpp2py {

using is_wrapped_type = void; // to recognize

static_assert(not std::is_reference_v<T>, "Not implemented");

template <typename U> static PyObject *c2py(U &&x) {
PyTypeObject *p = get_type_ptr(typeid(T));
if (p == nullptr) return NULL;
Expand Down
1 change: 1 addition & 0 deletions c++/cpp2py/pyref.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ namespace cpp2py {
return i;
}
};
static_assert(sizeof(pyref) == sizeof(PyObject *), "pyref must contain only a PyObject *");

// FIXME : put static or the other functions inline ?

Expand Down
11 changes: 11 additions & 0 deletions c++/cpp2py/traits.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#pragma once
#include <type_traits>

namespace cpp2py {

template <template <typename...> class TMPLT, typename T> struct is_instantiation_of : std::false_type {};
template <template <typename...> class TMPLT, typename... U> struct is_instantiation_of<TMPLT, TMPLT<U...>> : std::true_type {};
template <template <typename...> class gf, typename T>
inline constexpr bool is_instantiation_of_v = is_instantiation_of<gf, std::decay_t<T>>::value;

} // namespace cpp2py

0 comments on commit 721fbc7

Please sign in to comment.