Skip to content

Commit b97d1d2

Browse files
committed
core: Avoid ambiguity with numeric type conversion
1 parent 1ad70b4 commit b97d1d2

File tree

3 files changed

+139
-125
lines changed

3 files changed

+139
-125
lines changed

include/eigenpy/variant.hpp

Lines changed: 93 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -36,32 +36,6 @@ struct empty_variant {};
3636
template <typename T>
3737
struct is_empty_variant : std::false_type {};
3838

39-
/// Convert None to a {boost,std}::variant with boost::blank or std::monostate
40-
/// value
41-
template <typename Variant>
42-
struct EmptyConvertible {
43-
static void registration() {
44-
bp::converter::registry::push_back(convertible, construct,
45-
bp::type_id<Variant>());
46-
}
47-
48-
// convertible only for None
49-
static void* convertible(PyObject* obj) {
50-
return (obj == Py_None) ? obj : nullptr;
51-
};
52-
53-
// construct in place
54-
static void construct(PyObject*,
55-
bp::converter::rvalue_from_python_stage1_data* data) {
56-
void* storage =
57-
reinterpret_cast<bp::converter::rvalue_from_python_storage<Variant>*>(
58-
data)
59-
->storage.bytes;
60-
new (storage) Variant(typename empty_variant<Variant>::type());
61-
data->convertible = storage;
62-
};
63-
};
64-
6539
#ifdef EIGENPY_WITH_CXX17_SUPPORT
6640

6741
/// std::variant implementation
@@ -126,6 +100,90 @@ struct empty_variant<boost::variant<Alternatives...> > {
126100
template <>
127101
struct is_empty_variant<boost::blank> : std::true_type {};
128102

103+
/// Convert None to a {boost,std}::variant with boost::blank or std::monostate
104+
/// value
105+
template <typename Variant>
106+
struct EmptyConvertible {
107+
static void registration() {
108+
bp::converter::registry::push_back(convertible, construct,
109+
bp::type_id<Variant>());
110+
}
111+
112+
// convertible only for None
113+
static void* convertible(PyObject* obj) {
114+
return (obj == Py_None) ? obj : nullptr;
115+
};
116+
117+
// construct in place
118+
static void construct(PyObject*,
119+
bp::converter::rvalue_from_python_stage1_data* data) {
120+
void* storage =
121+
reinterpret_cast<bp::converter::rvalue_from_python_storage<Variant>*>(
122+
data)
123+
->storage.bytes;
124+
new (storage) Variant(typename empty_variant<Variant>::type());
125+
data->convertible = storage;
126+
};
127+
};
128+
129+
/// Implement convertible and expected_pytype for bool, integer and float
130+
template <typename T, class Enable = void>
131+
struct NumericConvertibleImpl {};
132+
133+
template <typename T>
134+
struct NumericConvertibleImpl<
135+
T, typename std::enable_if<std::is_same<T, bool>::value>::type> {
136+
static void* convertible(PyObject* obj) {
137+
return PyBool_Check(obj) ? obj : nullptr;
138+
}
139+
140+
static PyTypeObject const* expected_pytype() { return &PyBool_Type; }
141+
};
142+
143+
template <typename T>
144+
struct NumericConvertibleImpl<
145+
T, typename std::enable_if<!std::is_same<T, bool>::value &&
146+
std::is_integral<T>::value>::type> {
147+
static void* convertible(PyObject* obj) {
148+
// PyLong return true for bool type
149+
return (PyLong_Check(obj) && !PyBool_Check(obj)) ? obj : nullptr;
150+
}
151+
152+
static PyTypeObject const* expected_pytype() { return &PyLong_Type; }
153+
};
154+
155+
template <typename T>
156+
struct NumericConvertibleImpl<
157+
T, typename std::enable_if<std::is_floating_point<T>::value>::type> {
158+
static void* convertible(PyObject* obj) {
159+
return PyFloat_Check(obj) ? obj : nullptr;
160+
}
161+
162+
static PyTypeObject const* expected_pytype() { return &PyFloat_Type; }
163+
};
164+
165+
/// Convert numeric type to Variant without ambiguity
166+
template <typename T, typename Variant>
167+
struct NumericConvertible {
168+
static void registration() {
169+
bp::converter::registry::push_back(
170+
&convertible, &bp::converter::implicit<T, Variant>::construct,
171+
bp::type_id<Variant>()
172+
#ifndef BOOST_PYTHON_NO_PY_SIGNATURES
173+
,
174+
&expected_pytype
175+
#endif
176+
);
177+
}
178+
179+
static void* convertible(PyObject* obj) {
180+
return NumericConvertibleImpl<T>::convertible(obj);
181+
}
182+
static PyTypeObject const* expected_pytype() {
183+
return NumericConvertibleImpl<T>::expected_pytype();
184+
}
185+
};
186+
129187
/// Convert {boost,std}::variant<class...> alternative to a Python object.
130188
/// This converter copy the alternative.
131189
template <typename Variant>
@@ -225,7 +283,15 @@ struct VariantConvertible {
225283
EmptyConvertible<variant_type>::registration();
226284
}
227285

228-
template <class T, typename std::enable_if<!is_empty_variant<T>::value,
286+
template <class T, typename std::enable_if<!is_empty_variant<T>::value &&
287+
std::is_arithmetic<T>::value,
288+
bool>::type = true>
289+
void operator()(T) {
290+
NumericConvertible<T, variant_type>::registration();
291+
}
292+
293+
template <class T, typename std::enable_if<!is_empty_variant<T>::value &&
294+
!std::is_arithmetic<T>::value,
229295
bool>::type = true>
230296
void operator()(T) {
231297
bp::implicitly_convertible<T, variant_type>();

unittest/python/test_variant.py.in

Lines changed: 34 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,9 @@ variant_module = importlib.import_module("@MODNAME@")
44
V1 = variant_module.V1
55
V2 = variant_module.V2
66
VariantHolder = variant_module.VariantHolder
7-
VariantNoneHolder = variant_module.VariantNoneHolder
8-
VariantArithmeticHolder = variant_module.VariantArithmeticHolder
9-
VariantBoolHolder = variant_module.VariantBoolHolder
7+
VariantFullHolder = variant_module.VariantFullHolder
108
make_variant = variant_module.make_variant
11-
make_variant_none = variant_module.make_variant_none
12-
make_variant_arithmetic = variant_module.make_variant_arithmetic
13-
make_variant_bool = variant_module.make_variant_bool
9+
make_variant_full = variant_module.make_variant_full
1410

1511
variant = make_variant()
1612
assert isinstance(variant, V1)
@@ -48,48 +44,40 @@ assert isinstance(variant_holder.variant, V2)
4844
assert variant_holder.variant.v == v2.v
4945

5046
# Test variant that hold a None value
51-
v_none = make_variant_none()
52-
assert v_none is None
47+
v_full = make_variant_full()
48+
assert v_full is None
49+
50+
variant_full_holder = VariantFullHolder()
5351

54-
variant_none_holder = VariantNoneHolder()
55-
v_none = variant_none_holder.variant
52+
# Test None
53+
v_none = variant_full_holder.variant
54+
assert v_none is None
55+
variant_full_holder.variant = None
5656
assert v_none is None
5757

58+
# Test V1
5859
v1 = V1()
59-
v1.v = 1
60-
variant_none_holder.variant = v1
61-
assert variant_none_holder.variant.v == 1
62-
v1 = variant_none_holder.variant
6360
v1.v = 10
64-
assert variant_none_holder.variant.v == 10
65-
variant_none_holder.variant = None
66-
67-
68-
# Test variant that hold base type
69-
v_arithmetic = make_variant_arithmetic()
70-
assert isinstance(v_arithmetic, int)
71-
72-
variant_arithmetic_holder = VariantArithmeticHolder()
73-
assert isinstance(variant_arithmetic_holder.variant, int)
74-
variant_arithmetic_holder.variant = 2
75-
# Raise an exception if return_internal_postcall is called
76-
assert variant_arithmetic_holder.variant == 2
77-
78-
variant_arithmetic_holder.variant = 2.0
79-
assert isinstance(variant_arithmetic_holder.variant, float)
80-
# Raise an exception if return_internal_postcall is called
81-
assert variant_arithmetic_holder.variant == 2.0
82-
83-
v_bool = make_variant_bool()
84-
assert isinstance(v_bool, bool)
85-
86-
variant_bool_holder = VariantBoolHolder()
87-
assert isinstance(variant_bool_holder.variant, bool)
88-
variant_bool_holder.variant = False
89-
# Raise an exception if return_internal_postcall is called
90-
assert not variant_bool_holder.variant
91-
92-
variant_bool_holder.variant = 2.0
93-
assert isinstance(variant_bool_holder.variant, float)
94-
# Raise an exception if return_internal_postcall is called
95-
assert variant_bool_holder.variant == 2.0
61+
variant_full_holder.variant = v1
62+
assert variant_full_holder.variant.v == 10
63+
assert isinstance(variant_full_holder.variant, V1)
64+
# Test V1 ref
65+
v1 = variant_full_holder.variant
66+
v1.v = 100
67+
assert variant_full_holder.variant.v == 100
68+
variant_full_holder.variant = None
69+
70+
# Test bool
71+
variant_full_holder.variant = True
72+
assert variant_full_holder.variant
73+
assert isinstance(variant_full_holder.variant, bool)
74+
75+
# Test int
76+
variant_full_holder.variant = 3
77+
assert variant_full_holder.variant == 3
78+
assert isinstance(variant_full_holder.variant, int)
79+
80+
# Test float
81+
variant_full_holder.variant = 3.14
82+
assert variant_full_holder.variant == 3.14
83+
assert isinstance(variant_full_holder.variant, float)

unittest/variant.cpp.in

Lines changed: 12 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -32,35 +32,19 @@ struct MyVariantNoneHelper<std::variant<Alternatives...> > {
3232
};
3333
#endif
3434

35-
typedef typename MyVariantNoneHelper<VARIANT<V1> >::type MyVariantNone;
36-
37-
typedef VARIANT<int, double> MyVariantArithmetic;
38-
39-
// There is a conversion conflict between int and bool
40-
typedef VARIANT<bool, double> MyVariantBool;
35+
typedef typename MyVariantNoneHelper<VARIANT<V1, bool, int, double> >::type
36+
MyVariantFull;
4137

4238
MyVariant make_variant() { return V1(); }
4339

44-
MyVariantNone make_variant_none() { return MyVariantNone(); }
45-
46-
MyVariantArithmetic make_variant_arithmetic() { return MyVariantArithmetic(); }
47-
48-
MyVariantBool make_variant_bool() { return MyVariantBool(); }
40+
MyVariantFull make_variant_full() { return MyVariantFull(); }
4941

5042
struct VariantHolder {
5143
MyVariant variant;
5244
};
5345

54-
struct VariantNoneHolder {
55-
MyVariantNone variant;
56-
};
57-
58-
struct VariantArithmeticHolder {
59-
MyVariantArithmetic variant;
60-
};
61-
62-
struct VariantBoolHolder {
63-
MyVariantBool variant;
46+
struct VariantFullHolder {
47+
MyVariantFull variant;
6448
};
6549

6650
BOOST_PYTHON_MODULE(@MODNAME@) {
@@ -82,37 +66,13 @@ BOOST_PYTHON_MODULE(@MODNAME@) {
8266
Converter::return_internal_reference()),
8367
bp::make_setter(&VariantHolder::variant));
8468

85-
typedef eigenpy::VariantConverter<MyVariantNone> ConverterNone;
86-
ConverterNone::registration();
87-
bp::def("make_variant_none", make_variant_none);
69+
typedef eigenpy::VariantConverter<MyVariantFull> ConverterFull;
70+
ConverterFull::registration();
71+
bp::def("make_variant_full", make_variant_full);
8872

89-
boost::python::class_<VariantNoneHolder>("VariantNoneHolder", bp::init<>())
73+
boost::python::class_<VariantFullHolder>("VariantFullHolder", bp::init<>())
9074
.add_property("variant",
91-
bp::make_getter(&VariantNoneHolder::variant,
92-
ConverterNone::return_internal_reference()),
93-
bp::make_setter(&VariantNoneHolder::variant));
94-
95-
typedef eigenpy::VariantConverter<MyVariantArithmetic> ConverterArithmetic;
96-
ConverterArithmetic::registration();
97-
bp::def("make_variant_arithmetic", make_variant_arithmetic);
98-
99-
boost::python::class_<VariantArithmeticHolder>("VariantArithmeticHolder",
100-
bp::init<>())
101-
.add_property(
102-
"variant",
103-
bp::make_getter(&VariantArithmeticHolder::variant,
104-
ConverterArithmetic::return_internal_reference()),
105-
bp::make_setter(&VariantArithmeticHolder::variant));
106-
107-
typedef eigenpy::VariantConverter<MyVariantBool> ConverterBool;
108-
ConverterBool::registration();
109-
bp::def("make_variant_bool", make_variant_bool);
110-
111-
boost::python::class_<VariantBoolHolder>("VariantBoolHolder",
112-
bp::init<>())
113-
.add_property(
114-
"variant",
115-
bp::make_getter(&VariantBoolHolder::variant,
116-
ConverterBool::return_internal_reference()),
117-
bp::make_setter(&VariantBoolHolder::variant));
75+
bp::make_getter(&VariantFullHolder::variant,
76+
ConverterFull::return_internal_reference()),
77+
bp::make_setter(&VariantFullHolder::variant));
11878
}

0 commit comments

Comments
 (0)