19
19
#include < vector>
20
20
#include < string>
21
21
22
+ #if defined(PYBIND11_TEST_BOOST)
23
+ #include < boost/optional.hpp>
24
+
25
+ namespace pybind11 { namespace detail {
26
+ template <typename T>
27
+ struct type_caster <boost::optional<T>> : optional_caster<boost::optional<T>> {};
28
+
29
+ template <>
30
+ struct type_caster <boost::none_t > : void_caster<boost::none_t > {};
31
+ }} // namespace pybind11::detail
32
+ #endif
33
+
22
34
// Test with `std::variant` in C++17 mode, or with `boost::variant` in C++11/14
23
35
#if defined(PYBIND11_HAS_VARIANT)
24
36
using std::variant;
@@ -67,6 +79,92 @@ struct OptionalHolder
67
79
};
68
80
69
81
82
+ enum class EnumType {
83
+ k0 = 0 ,
84
+ k1 = 1 ,
85
+ };
86
+
87
+ // This is used to test that return-by-ref and return-by-copy policies are
88
+ // handled properly for optional types. This is a regression test for a dangling
89
+ // reference issue. The issue seemed to require the enum value type to
90
+ // reproduce - it didn't seem to happen if the value type is just an integer.
91
+ template <template <typename > class OptionalImpl >
92
+ class OptionalProperties {
93
+ public:
94
+ using OptionalEnumValue = OptionalImpl<EnumType>;
95
+
96
+ OptionalProperties () : value(EnumType::k1) {}
97
+ ~OptionalProperties () {
98
+ // Reset value to detect use-after-destruction.
99
+ // This is set to a specific value rather than NULL to ensure that
100
+ // the memory that contains the value gets re-written.
101
+ value = EnumType::k0;
102
+ }
103
+
104
+ OptionalEnumValue& access_by_ref () { return value; }
105
+ OptionalEnumValue access_by_copy () { return value; }
106
+
107
+ private:
108
+ OptionalEnumValue value;
109
+ };
110
+
111
+ // This type mimics aspects of boost::optional from old versions of Boost,
112
+ // which exposed a dangling reference bug in Pybind11. Recent versions of
113
+ // boost::optional, as well as libstdc++'s std::optional, don't seem to be
114
+ // affected by the same issue. This is meant to be a minimal implementation
115
+ // required to reproduce the issue, not fully standard-compliant.
116
+ // See issue #3330 for more details.
117
+ template <typename T>
118
+ class ReferenceSensitiveOptional {
119
+ public:
120
+ using value_type = T;
121
+
122
+ ReferenceSensitiveOptional () = default ;
123
+ ReferenceSensitiveOptional (const T& value) : storage{value} {}
124
+ ReferenceSensitiveOptional (T&& value) : storage{std::move (value)} {}
125
+ ReferenceSensitiveOptional& operator =(const T& value) {
126
+ storage = {value};
127
+ return *this ;
128
+ }
129
+ ReferenceSensitiveOptional& operator =(T&& value) {
130
+ storage = {std::move (value)};
131
+ return *this ;
132
+ }
133
+
134
+ template <typename ... Args>
135
+ T& emplace (Args&&... args) {
136
+ storage.clear ();
137
+ storage.emplace_back (std::forward<Args>(args)...);
138
+ return storage.back ();
139
+ }
140
+
141
+ const T& value () const noexcept {
142
+ assert (!storage.empty ());
143
+ return storage[0 ];
144
+ }
145
+
146
+ const T& operator *() const noexcept {
147
+ return value ();
148
+ }
149
+
150
+ const T* operator ->() const noexcept {
151
+ return &value ();
152
+ }
153
+
154
+ explicit operator bool () const noexcept {
155
+ return !storage.empty ();
156
+ }
157
+
158
+ private:
159
+ std::vector<T> storage;
160
+ };
161
+
162
+ namespace pybind11 { namespace detail {
163
+ template <typename T>
164
+ struct type_caster <ReferenceSensitiveOptional<T>> : optional_caster<ReferenceSensitiveOptional<T>> {};
165
+ }} // namespace pybind11::detail
166
+
167
+
70
168
TEST_SUBMODULE (stl, m) {
71
169
// test_vector
72
170
m.def (" cast_vector" , []() { return std::vector<int >{1 }; });
@@ -145,6 +243,10 @@ TEST_SUBMODULE(stl, m) {
145
243
return v;
146
244
});
147
245
246
+ pybind11::enum_<EnumType>(m, " EnumType" )
247
+ .value (" k0" , EnumType::k0)
248
+ .value (" k1" , EnumType::k1);
249
+
148
250
// test_move_out_container
149
251
struct MoveOutContainer {
150
252
struct Value { int value; };
@@ -213,6 +315,12 @@ TEST_SUBMODULE(stl, m) {
213
315
.def (py::init<>())
214
316
.def_readonly (" member" , &opt_holder::member)
215
317
.def (" member_initialized" , &opt_holder::member_initialized);
318
+
319
+ using opt_props = OptionalProperties<std::optional>;
320
+ pybind11::class_<opt_props>(m, " OptionalProperties" )
321
+ .def (pybind11::init<>())
322
+ .def_property_readonly (" access_by_ref" , &opt_props::access_by_ref)
323
+ .def_property_readonly (" access_by_copy" , &opt_props::access_by_copy);
216
324
#endif
217
325
218
326
#ifdef PYBIND11_HAS_EXP_OPTIONAL
@@ -239,8 +347,76 @@ TEST_SUBMODULE(stl, m) {
239
347
.def (py::init<>())
240
348
.def_readonly (" member" , &opt_exp_holder::member)
241
349
.def (" member_initialized" , &opt_exp_holder::member_initialized);
350
+
351
+ using opt_exp_props = OptionalProperties<std::experimental::optional>;
352
+ pybind11::class_<opt_exp_props>(m, " OptionalExpProperties" )
353
+ .def (pybind11::init<>())
354
+ .def_property_readonly (" access_by_ref" , &opt_exp_props::access_by_ref)
355
+ .def_property_readonly (" access_by_copy" , &opt_exp_props::access_by_copy);
356
+ #endif
357
+
358
+ #if defined(PYBIND11_TEST_BOOST)
359
+ // test_boost_optional
360
+ m.attr (" has_boost_optional" ) = true ;
361
+
362
+ using boost_opt_int = boost::optional<int >;
363
+ using boost_opt_no_assign = boost::optional<NoAssign>;
364
+ m.def (" double_or_zero_boost" , [](const boost_opt_int& x) -> int {
365
+ return x.value_or (0 ) * 2 ;
366
+ });
367
+ m.def (" half_or_none_boost" , [](int x) -> boost_opt_int {
368
+ return x ? boost_opt_int (x / 2 ) : boost_opt_int ();
369
+ });
370
+ m.def (" test_nullopt_boost" , [](boost_opt_int x) {
371
+ return x.value_or (42 );
372
+ }, py::arg_v (" x" , boost::none, " None" ));
373
+ m.def (" test_no_assign_boost" , [](const boost_opt_no_assign &x) {
374
+ return x ? x->value : 42 ;
375
+ }, py::arg_v (" x" , boost::none, " None" ));
376
+
377
+ using opt_boost_holder = OptionalHolder<boost::optional, MoveOutDetector>;
378
+ py::class_<opt_boost_holder>(m, " OptionalBoostHolder" , " Class with optional member" )
379
+ .def (py::init<>())
380
+ .def_readonly (" member" , &opt_boost_holder::member)
381
+ .def (" member_initialized" , &opt_boost_holder::member_initialized);
382
+
383
+ using opt_boost_props = OptionalProperties<boost::optional>;
384
+ pybind11::class_<opt_boost_props>(m, " OptionalBoostProperties" )
385
+ .def (pybind11::init<>())
386
+ .def_property_readonly (" access_by_ref" , &opt_boost_props::access_by_ref)
387
+ .def_property_readonly (" access_by_copy" , &opt_boost_props::access_by_copy);
242
388
#endif
243
389
390
+ // test_refsensitive_optional
391
+ m.attr (" has_refsensitive_optional" ) = true ;
392
+
393
+ using refsensitive_opt_int = ReferenceSensitiveOptional<int >;
394
+ using refsensitive_opt_no_assign = ReferenceSensitiveOptional<NoAssign>;
395
+ m.def (" double_or_zero_refsensitive" , [](const refsensitive_opt_int& x) -> int {
396
+ return (x ? x.value () : 0 ) * 2 ;
397
+ });
398
+ m.def (" half_or_none_refsensitive" , [](int x) -> refsensitive_opt_int {
399
+ return x ? refsensitive_opt_int (x / 2 ) : refsensitive_opt_int ();
400
+ });
401
+ m.def (" test_nullopt_refsensitive" , [](refsensitive_opt_int x) {
402
+ return x ? x.value () : 42 ;
403
+ }, py::arg_v (" x" , refsensitive_opt_int (), " None" ));
404
+ m.def (" test_no_assign_refsensitive" , [](const refsensitive_opt_no_assign &x) {
405
+ return x ? x->value : 42 ;
406
+ }, py::arg_v (" x" , refsensitive_opt_no_assign (), " None" ));
407
+
408
+ using opt_refsensitive_holder = OptionalHolder<ReferenceSensitiveOptional, MoveOutDetector>;
409
+ py::class_<opt_refsensitive_holder>(m, " OptionalRefSensitiveHolder" , " Class with optional member" )
410
+ .def (py::init<>())
411
+ .def_readonly (" member" , &opt_refsensitive_holder::member)
412
+ .def (" member_initialized" , &opt_refsensitive_holder::member_initialized);
413
+
414
+ using opt_refsensitive_props = OptionalProperties<ReferenceSensitiveOptional>;
415
+ pybind11::class_<opt_refsensitive_props>(m, " OptionalRefSensitiveProperties" )
416
+ .def (pybind11::init<>())
417
+ .def_property_readonly (" access_by_ref" , &opt_refsensitive_props::access_by_ref)
418
+ .def_property_readonly (" access_by_copy" , &opt_refsensitive_props::access_by_copy);
419
+
244
420
#ifdef PYBIND11_HAS_FILESYSTEM
245
421
// test_fs_path
246
422
m.attr (" has_filesystem" ) = true ;
@@ -280,8 +456,12 @@ TEST_SUBMODULE(stl, m) {
280
456
m.def (" tpl_ctor_set" , [](std::unordered_set<TplCtorClass> &) {});
281
457
#if defined(PYBIND11_HAS_OPTIONAL)
282
458
m.def (" tpl_constr_optional" , [](std::optional<TplCtorClass> &) {});
283
- #elif defined(PYBIND11_HAS_EXP_OPTIONAL)
284
- m.def (" tpl_constr_optional" , [](std::experimental::optional<TplCtorClass> &) {});
459
+ #endif
460
+ #if defined(PYBIND11_HAS_EXP_OPTIONAL)
461
+ m.def (" tpl_constr_optional_exp" , [](std::experimental::optional<TplCtorClass> &) {});
462
+ #endif
463
+ #if defined(PYBIND11_TEST_BOOST)
464
+ m.def (" tpl_constr_optional_boost" , [](boost::optional<TplCtorClass> &) {});
285
465
#endif
286
466
287
467
// test_vec_of_reference_wrapper
0 commit comments