From 9a57aff1577aee73141fe92f8ad467a75706319c Mon Sep 17 00:00:00 2001 From: Giovanni Funchal Date: Fri, 8 Dec 2017 12:21:38 +0000 Subject: [PATCH 1/3] Separate optional and variant support from other STL containers #1207 --- CMakeLists.txt | 1 + docs/advanced/cast/overview.rst | 6 +- docs/advanced/cast/stl.rst | 7 +- docs/changelog.rst | 5 + include/pybind11/pybind11.h | 6 +- include/pybind11/stl.h | 136 +------------------------- include/pybind11/utility.h | 164 ++++++++++++++++++++++++++++++++ setup.py | 1 + tests/CMakeLists.txt | 2 + tests/test_stl.cpp | 97 ------------------- tests/test_stl.py | 65 +------------ tests/test_utility.cpp | 139 +++++++++++++++++++++++++++ tests/test_utility.py | 64 +++++++++++++ 13 files changed, 392 insertions(+), 301 deletions(-) create mode 100644 include/pybind11/utility.h create mode 100644 tests/test_utility.cpp create mode 100644 tests/test_utility.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 4280ba742d..f4cde06a58 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -64,6 +64,7 @@ set(PYBIND11_HEADERS include/pybind11/pytypes.h include/pybind11/stl.h include/pybind11/stl_bind.h + include/pybind11/utility.h ) string(REPLACE "include/" "${CMAKE_CURRENT_SOURCE_DIR}/include/" PYBIND11_HEADERS "${PYBIND11_HEADERS}") diff --git a/docs/advanced/cast/overview.rst b/docs/advanced/cast/overview.rst index 2ac7d30097..5e7cf75ce0 100644 --- a/docs/advanced/cast/overview.rst +++ b/docs/advanced/cast/overview.rst @@ -143,11 +143,11 @@ as arguments and return values, refer to the section on binding :ref:`classes`. +------------------------------------+---------------------------+-------------------------------+ | ``std::unordered_set`` | STL unordered set | :file:`pybind11/stl.h` | +------------------------------------+---------------------------+-------------------------------+ -| ``std::optional`` | STL optional type (C++17) | :file:`pybind11/stl.h` | +| ``std::optional`` | STL optional type (C++17) | :file:`pybind11/utility.h` | +------------------------------------+---------------------------+-------------------------------+ -| ``std::experimental::optional`` | STL optional type (exp.) | :file:`pybind11/stl.h` | +| ``std::experimental::optional`` | STL optional type (exp.) | :file:`pybind11/utility.h` | +------------------------------------+---------------------------+-------------------------------+ -| ``std::variant<...>`` | Type-safe union (C++17) | :file:`pybind11/stl.h` | +| ``std::variant<...>`` | Type-safe union (C++17) | :file:`pybind11/utility.h` | +------------------------------------+---------------------------+-------------------------------+ | ``std::function<...>`` | STL polymorphic function | :file:`pybind11/functional.h` | +------------------------------------+---------------------------+-------------------------------+ diff --git a/docs/advanced/cast/stl.rst b/docs/advanced/cast/stl.rst index 3f30c0290d..a539423c58 100644 --- a/docs/advanced/cast/stl.rst +++ b/docs/advanced/cast/stl.rst @@ -31,10 +31,15 @@ next sections for more details and alternative approaches that avoid this. C++17 library containers ======================== -The :file:`pybind11/stl.h` header also includes support for ``std::optional<>`` +The :file:`pybind11/utility.h` header includes support for ``std::optional<>`` and ``std::variant<>``. These require a C++17 compiler and standard library. In C++14 mode, ``std::experimental::optional<>`` is supported if available. +.. note:: + + These features are also enabled by the header file :file:`pybind11/stl.h` + for backward compatibility. + Various versions of these containers also exist for C++11 (e.g. in Boost). pybind11 provides an easy way to specialize the ``type_caster`` for such types: diff --git a/docs/changelog.rst b/docs/changelog.rst index ec8dc72039..bbfdf28329 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -21,6 +21,11 @@ v2.3.0 (Not yet released) * The ``value()`` method of ``py::enum_`` now accepts an optional docstring that will be shown in the documentation of the associated enumeration. +* Added `pybind11/utility.h`, which enables support for STL utility types + (`std::optional` and `std::variant`) separately from STL containers. + `pybind11/stl.h` enables both. + `#1207 `_. + v2.2.1 (September 14, 2017) ----------------------------------------------------- diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index 5bd2cccc4d..24040a43b2 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -687,9 +687,9 @@ class cpp_function : public function { if (msg.find("std::") != std::string::npos) { msg += "\n\n" "Did you forget to `#include `? Or ,\n" - ", , etc. Some automatic\n" - "conversions are optional and require extra headers to be included\n" - "when compiling your pybind11 module."; + ", , , etc.\n" + "Some automatic conversions are optional and require extra headers to be\n" + "included when compiling your pybind11 module."; } }; diff --git a/include/pybind11/stl.h b/include/pybind11/stl.h index e690e43fad..88d5a97111 100644 --- a/include/pybind11/stl.h +++ b/include/pybind11/stl.h @@ -10,6 +10,7 @@ #pragma once #include "pybind11.h" +#include "utility.h" #include #include #include @@ -23,29 +24,6 @@ #pragma warning(disable: 4127) // warning C4127: Conditional expression is constant #endif -#ifdef __has_include -// std::optional (but including it in c++14 mode isn't allowed) -# if defined(PYBIND11_CPP17) && __has_include() -# include -# define PYBIND11_HAS_OPTIONAL 1 -# endif -// std::experimental::optional (but not allowed in c++11 mode) -# if defined(PYBIND11_CPP14) && __has_include() -# include -# define PYBIND11_HAS_EXP_OPTIONAL 1 -# endif -// std::variant -# if defined(PYBIND11_CPP17) && __has_include() -# include -# define PYBIND11_HAS_VARIANT 1 -# endif -#elif defined(_MSC_VER) && defined(PYBIND11_CPP17) -# include -# include -# define PYBIND11_HAS_OPTIONAL 1 -# define PYBIND11_HAS_VARIANT 1 -#endif - NAMESPACE_BEGIN(PYBIND11_NAMESPACE) NAMESPACE_BEGIN(detail) @@ -243,118 +221,6 @@ template struct template struct type_caster> : map_caster, Key, Value> { }; -// This type caster is intended to be used for std::optional and std::experimental::optional -template struct optional_caster { - using value_conv = make_caster; - - template - static handle cast(T_ &&src, return_value_policy policy, handle parent) { - if (!src) - return none().inc_ref(); - return value_conv::cast(*std::forward(src), policy, parent); - } - - bool load(handle src, bool convert) { - if (!src) { - return false; - } else if (src.is_none()) { - return true; // default-constructed value is already empty - } - value_conv inner_caster; - if (!inner_caster.load(src, convert)) - return false; - - value.emplace(cast_op(std::move(inner_caster))); - return true; - } - - PYBIND11_TYPE_CASTER(T, _("Optional[") + value_conv::name + _("]")); -}; - -#if PYBIND11_HAS_OPTIONAL -template struct type_caster> - : public optional_caster> {}; - -template<> struct type_caster - : public void_caster {}; -#endif - -#if PYBIND11_HAS_EXP_OPTIONAL -template struct type_caster> - : public optional_caster> {}; - -template<> struct type_caster - : public void_caster {}; -#endif - -/// Visit a variant and cast any found type to Python -struct variant_caster_visitor { - return_value_policy policy; - handle parent; - - using result_type = handle; // required by boost::variant in C++11 - - template - result_type operator()(T &&src) const { - return make_caster::cast(std::forward(src), policy, parent); - } -}; - -/// Helper class which abstracts away variant's `visit` function. `std::variant` and similar -/// `namespace::variant` types which provide a `namespace::visit()` function are handled here -/// automatically using argument-dependent lookup. Users can provide specializations for other -/// variant-like classes, e.g. `boost::variant` and `boost::apply_visitor`. -template class Variant> -struct visit_helper { - template - static auto call(Args &&...args) -> decltype(visit(std::forward(args)...)) { - return visit(std::forward(args)...); - } -}; - -/// Generic variant caster -template struct variant_caster; - -template class V, typename... Ts> -struct variant_caster> { - static_assert(sizeof...(Ts) > 0, "Variant must consist of at least one alternative."); - - template - bool load_alternative(handle src, bool convert, type_list) { - auto caster = make_caster(); - if (caster.load(src, convert)) { - value = cast_op(caster); - return true; - } - return load_alternative(src, convert, type_list{}); - } - - bool load_alternative(handle, bool, type_list<>) { return false; } - - bool load(handle src, bool convert) { - // Do a first pass without conversions to improve constructor resolution. - // E.g. `py::int_(1).cast>()` needs to fill the `int` - // slot of the variant. Without two-pass loading `double` would be filled - // because it appears first and a conversion is possible. - if (convert && load_alternative(src, false, type_list{})) - return true; - return load_alternative(src, convert, type_list{}); - } - - template - static handle cast(Variant &&src, return_value_policy policy, handle parent) { - return visit_helper::call(variant_caster_visitor{policy, parent}, - std::forward(src)); - } - - using Type = V; - PYBIND11_TYPE_CASTER(Type, _("Union[") + detail::concat(make_caster::name...) + _("]")); -}; - -#if PYBIND11_HAS_VARIANT -template -struct type_caster> : variant_caster> { }; -#endif NAMESPACE_END(detail) inline std::ostream &operator<<(std::ostream &os, const handle &obj) { diff --git a/include/pybind11/utility.h b/include/pybind11/utility.h new file mode 100644 index 0000000000..8add311b6e --- /dev/null +++ b/include/pybind11/utility.h @@ -0,0 +1,164 @@ +/* + pybind11/utility.h: Support for STL utility types (variant, optional) + + Copyright (c) 2016 Wenzel Jakob + + All rights reserved. Use of this source code is governed by a + BSD-style license that can be found in the LICENSE file. +*/ + +#pragma once + +#include "pybind11.h" + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable: 4127) // warning C4127: Conditional expression is constant +#endif + +#ifdef __has_include +// std::optional (but including it in c++14 mode isn't allowed) +# if defined(PYBIND11_CPP17) && __has_include() +# include +# define PYBIND11_HAS_OPTIONAL 1 +# endif +// std::experimental::optional (but not allowed in c++11 mode) +# if defined(PYBIND11_CPP14) && __has_include() +# include +# define PYBIND11_HAS_EXP_OPTIONAL 1 +# endif +// std::variant +# if defined(PYBIND11_CPP17) && __has_include() +# include +# define PYBIND11_HAS_VARIANT 1 +# endif +#elif defined(_MSC_VER) && defined(PYBIND11_CPP17) +# include +# include +# define PYBIND11_HAS_OPTIONAL 1 +# define PYBIND11_HAS_VARIANT 1 +#endif + +NAMESPACE_BEGIN(PYBIND11_NAMESPACE) +NAMESPACE_BEGIN(detail) + +// This type caster is intended to be used for std::optional and std::experimental::optional +template struct optional_caster { + using value_conv = make_caster; + + template + static handle cast(T_ &&src, return_value_policy policy, handle parent) { + if (!src) + return none().inc_ref(); + return value_conv::cast(*std::forward(src), policy, parent); + } + + bool load(handle src, bool convert) { + if (!src) { + return false; + } else if (src.is_none()) { + return true; // default-constructed value is already empty + } + value_conv inner_caster; + if (!inner_caster.load(src, convert)) + return false; + + value.emplace(cast_op(std::move(inner_caster))); + return true; + } + + PYBIND11_TYPE_CASTER(T, _("Optional[") + value_conv::name + _("]")); +}; + +#if PYBIND11_HAS_OPTIONAL +template struct type_caster> + : public optional_caster> {}; + +template<> struct type_caster + : public void_caster {}; +#endif + +#if PYBIND11_HAS_EXP_OPTIONAL +template struct type_caster> + : public optional_caster> {}; + +template<> struct type_caster + : public void_caster {}; +#endif + +/// Visit a variant and cast any found type to Python +struct variant_caster_visitor { + return_value_policy policy; + handle parent; + + using result_type = handle; // required by boost::variant in C++11 + + template + result_type operator()(T &&src) const { + return make_caster::cast(std::forward(src), policy, parent); + } +}; + +/// Helper class which abstracts away variant's `visit` function. `std::variant` and similar +/// `namespace::variant` types which provide a `namespace::visit()` function are handled here +/// automatically using argument-dependent lookup. Users can provide specializations for other +/// variant-like classes, e.g. `boost::variant` and `boost::apply_visitor`. +template class Variant> +struct visit_helper { + template + static auto call(Args &&...args) -> decltype(visit(std::forward(args)...)) { + return visit(std::forward(args)...); + } +}; + +/// Generic variant caster +template struct variant_caster; + +template class V, typename... Ts> +struct variant_caster> { + static_assert(sizeof...(Ts) > 0, "Variant must consist of at least one alternative."); + + template + bool load_alternative(handle src, bool convert, type_list) { + auto caster = make_caster(); + if (caster.load(src, convert)) { + value = cast_op(caster); + return true; + } + return load_alternative(src, convert, type_list{}); + } + + bool load_alternative(handle, bool, type_list<>) { return false; } + + bool load(handle src, bool convert) { + // Do a first pass without conversions to improve constructor resolution. + // E.g. `py::int_(1).cast>()` needs to fill the `int` + // slot of the variant. Without two-pass loading `double` would be filled + // because it appears first and a conversion is possible. + if (convert && load_alternative(src, false, type_list{})) + return true; + return load_alternative(src, convert, type_list{}); + } + + template + static handle cast(Variant &&src, return_value_policy policy, handle parent) { + return visit_helper::call(variant_caster_visitor{policy, parent}, + std::forward(src)); + } + + using Type = V; + PYBIND11_TYPE_CASTER(Type, _("Union[") + detail::concat(make_caster::name...) + _("]")); +}; + +#if PYBIND11_HAS_VARIANT +template +struct type_caster> : variant_caster> { }; +#endif + +NAMESPACE_END(detail) + +NAMESPACE_END(PYBIND11_NAMESPACE) + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif diff --git a/setup.py b/setup.py index b761205737..71341100f2 100644 --- a/setup.py +++ b/setup.py @@ -37,6 +37,7 @@ 'include/pybind11/pytypes.h', 'include/pybind11/stl.h', 'include/pybind11/stl_bind.h', + 'include/pybind11/utility.h', ] diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 8f2f300ef7..4d0746db0a 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -58,6 +58,7 @@ set(PYBIND11_TEST_FILES test_stl.cpp test_stl_binders.cpp test_virtual_functions.cpp + test_utility.cpp ) # Invoking cmake with something like: @@ -78,6 +79,7 @@ set(PYBIND11_CROSS_MODULE_TESTS test_local_bindings.py test_stl.py test_stl_binders.py + test_utility.py ) # Check if Eigen is available; if not, remove from PYBIND11_TEST_FILES (but diff --git a/tests/test_stl.cpp b/tests/test_stl.cpp index 7d53e9c18d..eb6adbdb06 100644 --- a/tests/test_stl.cpp +++ b/tests/test_stl.cpp @@ -10,28 +10,6 @@ #include "pybind11_tests.h" #include -// Test with `std::variant` in C++17 mode, or with `boost::variant` in C++11/14 -#if PYBIND11_HAS_VARIANT -using std::variant; -#elif defined(PYBIND11_TEST_BOOST) && (!defined(_MSC_VER) || _MSC_VER >= 1910) -# include -# define PYBIND11_HAS_VARIANT 1 -using boost::variant; - -namespace pybind11 { namespace detail { -template -struct type_caster> : variant_caster> {}; - -template <> -struct visit_helper { - template - static auto call(Args &&...args) -> decltype(boost::apply_visitor(args...)) { - return boost::apply_visitor(args...); - } -}; -}} // namespace pybind11::detail -#endif - /// Issue #528: templated constructor struct TplCtorClass { template TplCtorClass(const T &) { } @@ -43,7 +21,6 @@ namespace std { struct hash { size_t operator()(const TplCtorClass &) const { return 0; } }; } - TEST_SUBMODULE(stl, m) { // test_vector m.def("cast_vector", []() { return std::vector{1}; }); @@ -144,85 +121,11 @@ TEST_SUBMODULE(stl, m) { .def(py::init<>()) .def(py::init()); -#ifdef PYBIND11_HAS_OPTIONAL - // test_optional - m.attr("has_optional") = true; - - using opt_int = std::optional; - using opt_no_assign = std::optional; - m.def("double_or_zero", [](const opt_int& x) -> int { - return x.value_or(0) * 2; - }); - m.def("half_or_none", [](int x) -> opt_int { - return x ? opt_int(x / 2) : opt_int(); - }); - m.def("test_nullopt", [](opt_int x) { - return x.value_or(42); - }, py::arg_v("x", std::nullopt, "None")); - m.def("test_no_assign", [](const opt_no_assign &x) { - return x ? x->value : 42; - }, py::arg_v("x", std::nullopt, "None")); - - m.def("nodefer_none_optional", [](std::optional) { return true; }); - m.def("nodefer_none_optional", [](py::none) { return false; }); -#endif - -#ifdef PYBIND11_HAS_EXP_OPTIONAL - // test_exp_optional - m.attr("has_exp_optional") = true; - - using exp_opt_int = std::experimental::optional; - using exp_opt_no_assign = std::experimental::optional; - m.def("double_or_zero_exp", [](const exp_opt_int& x) -> int { - return x.value_or(0) * 2; - }); - m.def("half_or_none_exp", [](int x) -> exp_opt_int { - return x ? exp_opt_int(x / 2) : exp_opt_int(); - }); - m.def("test_nullopt_exp", [](exp_opt_int x) { - return x.value_or(42); - }, py::arg_v("x", std::experimental::nullopt, "None")); - m.def("test_no_assign_exp", [](const exp_opt_no_assign &x) { - return x ? x->value : 42; - }, py::arg_v("x", std::experimental::nullopt, "None")); -#endif - -#ifdef PYBIND11_HAS_VARIANT - static_assert(std::is_same::value, - "visitor::result_type is required by boost::variant in C++11 mode"); - - struct visitor { - using result_type = const char *; - - result_type operator()(int) { return "int"; } - result_type operator()(std::string) { return "std::string"; } - result_type operator()(double) { return "double"; } - result_type operator()(std::nullptr_t) { return "std::nullptr_t"; } - }; - - // test_variant - m.def("load_variant", [](variant v) { - return py::detail::visit_helper::call(visitor(), v); - }); - m.def("load_variant_2pass", [](variant v) { - return py::detail::visit_helper::call(visitor(), v); - }); - m.def("cast_variant", []() { - using V = variant; - return py::make_tuple(V(5), V("Hello")); - }); -#endif - // #528: templated constructor // (no python tests: the test here is that this compiles) m.def("tpl_ctor_vector", [](std::vector &) {}); m.def("tpl_ctor_map", [](std::unordered_map &) {}); m.def("tpl_ctor_set", [](std::unordered_set &) {}); -#if defined(PYBIND11_HAS_OPTIONAL) - m.def("tpl_constr_optional", [](std::optional &) {}); -#elif defined(PYBIND11_HAS_EXP_OPTIONAL) - m.def("tpl_constr_optional", [](std::experimental::optional &) {}); -#endif // test_vec_of_reference_wrapper // #171: Can't return STL structures containing reference wrapper diff --git a/tests/test_stl.py b/tests/test_stl.py index fbf95ff06e..d6324908e9 100644 --- a/tests/test_stl.py +++ b/tests/test_stl.py @@ -93,65 +93,6 @@ def test_move_out_container(): assert [x.value for x in moved_out_list] == [0, 1, 2] -@pytest.mark.skipif(not hasattr(m, "has_optional"), reason='no ') -def test_optional(): - assert m.double_or_zero(None) == 0 - assert m.double_or_zero(42) == 84 - pytest.raises(TypeError, m.double_or_zero, 'foo') - - assert m.half_or_none(0) is None - assert m.half_or_none(42) == 21 - pytest.raises(TypeError, m.half_or_none, 'foo') - - assert m.test_nullopt() == 42 - assert m.test_nullopt(None) == 42 - assert m.test_nullopt(42) == 42 - assert m.test_nullopt(43) == 43 - - assert m.test_no_assign() == 42 - assert m.test_no_assign(None) == 42 - assert m.test_no_assign(m.NoAssign(43)) == 43 - pytest.raises(TypeError, m.test_no_assign, 43) - - assert m.nodefer_none_optional(None) - - -@pytest.mark.skipif(not hasattr(m, "has_exp_optional"), reason='no ') -def test_exp_optional(): - assert m.double_or_zero_exp(None) == 0 - assert m.double_or_zero_exp(42) == 84 - pytest.raises(TypeError, m.double_or_zero_exp, 'foo') - - assert m.half_or_none_exp(0) is None - assert m.half_or_none_exp(42) == 21 - pytest.raises(TypeError, m.half_or_none_exp, 'foo') - - assert m.test_nullopt_exp() == 42 - assert m.test_nullopt_exp(None) == 42 - assert m.test_nullopt_exp(42) == 42 - assert m.test_nullopt_exp(43) == 43 - - assert m.test_no_assign_exp() == 42 - assert m.test_no_assign_exp(None) == 42 - assert m.test_no_assign_exp(m.NoAssign(43)) == 43 - pytest.raises(TypeError, m.test_no_assign_exp, 43) - - -@pytest.mark.skipif(not hasattr(m, "load_variant"), reason='no ') -def test_variant(doc): - assert m.load_variant(1) == "int" - assert m.load_variant("1") == "std::string" - assert m.load_variant(1.0) == "double" - assert m.load_variant(None) == "std::nullptr_t" - - assert m.load_variant_2pass(1) == "int" - assert m.load_variant_2pass(1.0) == "double" - - assert m.cast_variant() == (5, "Hello") - - assert doc(m.load_variant) == "load_variant(arg0: Union[int, str, float, None]) -> str" - - def test_vec_of_reference_wrapper(): """#171: Can't return reference wrappers (or STL structures containing them)""" assert str(m.return_vec_of_reference_wrapper(UserType(4))) == \ @@ -187,9 +128,9 @@ def test_missing_header_message(): import pybind11_cross_module_tests as cm expected_message = ("Did you forget to `#include `? Or ,\n" - ", , etc. Some automatic\n" - "conversions are optional and require extra headers to be included\n" - "when compiling your pybind11 module.") + ", , , etc.\n" + "Some automatic conversions are optional and require extra headers to be\n" + "included when compiling your pybind11 module.") with pytest.raises(TypeError) as excinfo: cm.missing_header_arg([1.0, 2.0, 3.0]) diff --git a/tests/test_utility.cpp b/tests/test_utility.cpp new file mode 100644 index 0000000000..131de3f031 --- /dev/null +++ b/tests/test_utility.cpp @@ -0,0 +1,139 @@ +/* + tests/test_stl.cpp -- STL utility support + + Copyright (c) 2017 Wenzel Jakob + + All rights reserved. Use of this source code is governed by a + BSD-style license that can be found in the LICENSE file. +*/ + +#include "pybind11_tests.h" +#include + +// Test with `std::variant` in C++17 mode, or with `boost::variant` in C++11/14 +#if PYBIND11_HAS_VARIANT +using std::variant; +#elif defined(PYBIND11_TEST_BOOST) && (!defined(_MSC_VER) || _MSC_VER >= 1910) +# include +# define PYBIND11_HAS_VARIANT 1 +using boost::variant; + +namespace pybind11 { namespace detail { +template +struct type_caster> : variant_caster> {}; + +template <> +struct visit_helper { + template + static auto call(Args &&...args) -> decltype(boost::apply_visitor(args...)) { + return boost::apply_visitor(args...); + } +}; +}} // namespace pybind11::detail +#endif + +/// Issue #528: templated constructor +struct TplCtorClass { + template TplCtorClass(const T &) { } + bool operator==(const TplCtorClass &) const { return true; } +}; + +namespace std { + template <> + struct hash { size_t operator()(const TplCtorClass &) const { return 0; } }; +} + + +TEST_SUBMODULE(utility, m) { + // Class that can be move- and copy-constructed, but not assigned + struct NoAssign { + int value; + + explicit NoAssign(int value = 0) : value(value) { } + NoAssign(const NoAssign &) = default; + NoAssign(NoAssign &&) = default; + + NoAssign &operator=(const NoAssign &) = delete; + NoAssign &operator=(NoAssign &&) = delete; + }; + py::class_(m, "NoAssign", "Class with no C++ assignment operators") + .def(py::init<>()) + .def(py::init()); + +#ifdef PYBIND11_HAS_OPTIONAL + // test_optional + m.attr("has_optional") = true; + + using opt_int = std::optional; + using opt_no_assign = std::optional; + m.def("double_or_zero", [](const opt_int& x) -> int { + return x.value_or(0) * 2; + }); + m.def("half_or_none", [](int x) -> opt_int { + return x ? opt_int(x / 2) : opt_int(); + }); + m.def("test_nullopt", [](opt_int x) { + return x.value_or(42); + }, py::arg_v("x", std::nullopt, "None")); + m.def("test_no_assign", [](const opt_no_assign &x) { + return x ? x->value : 42; + }, py::arg_v("x", std::nullopt, "None")); + + m.def("nodefer_none_optional", [](std::optional) { return true; }); + m.def("nodefer_none_optional", [](py::none) { return false; }); +#endif + +#ifdef PYBIND11_HAS_EXP_OPTIONAL + // test_exp_optional + m.attr("has_exp_optional") = true; + + using exp_opt_int = std::experimental::optional; + using exp_opt_no_assign = std::experimental::optional; + m.def("double_or_zero_exp", [](const exp_opt_int& x) -> int { + return x.value_or(0) * 2; + }); + m.def("half_or_none_exp", [](int x) -> exp_opt_int { + return x ? exp_opt_int(x / 2) : exp_opt_int(); + }); + m.def("test_nullopt_exp", [](exp_opt_int x) { + return x.value_or(42); + }, py::arg_v("x", std::experimental::nullopt, "None")); + m.def("test_no_assign_exp", [](const exp_opt_no_assign &x) { + return x ? x->value : 42; + }, py::arg_v("x", std::experimental::nullopt, "None")); +#endif + +#ifdef PYBIND11_HAS_VARIANT + static_assert(std::is_same::value, + "visitor::result_type is required by boost::variant in C++11 mode"); + + struct visitor { + using result_type = const char *; + + result_type operator()(int) { return "int"; } + result_type operator()(std::string) { return "std::string"; } + result_type operator()(double) { return "double"; } + result_type operator()(std::nullptr_t) { return "std::nullptr_t"; } + }; + + // test_variant + m.def("load_variant", [](variant v) { + return py::detail::visit_helper::call(visitor(), v); + }); + m.def("load_variant_2pass", [](variant v) { + return py::detail::visit_helper::call(visitor(), v); + }); + m.def("cast_variant", []() { + using V = variant; + return py::make_tuple(V(5), V("Hello")); + }); +#endif + + // #528: templated constructor + // (no python tests: the test here is that this compiles) +#if defined(PYBIND11_HAS_OPTIONAL) + m.def("tpl_constr_optional", [](std::optional &) {}); +#elif defined(PYBIND11_HAS_EXP_OPTIONAL) + m.def("tpl_constr_optional", [](std::experimental::optional &) {}); +#endif +} diff --git a/tests/test_utility.py b/tests/test_utility.py new file mode 100644 index 0000000000..14c705f28b --- /dev/null +++ b/tests/test_utility.py @@ -0,0 +1,64 @@ +import pytest + +from pybind11_tests import utility as m +from pybind11_tests import UserType + + +@pytest.mark.skipif(not hasattr(m, "has_optional"), reason='no ') +def test_optional(): + assert m.double_or_zero(None) == 0 + assert m.double_or_zero(42) == 84 + pytest.raises(TypeError, m.double_or_zero, 'foo') + + assert m.half_or_none(0) is None + assert m.half_or_none(42) == 21 + pytest.raises(TypeError, m.half_or_none, 'foo') + + assert m.test_nullopt() == 42 + assert m.test_nullopt(None) == 42 + assert m.test_nullopt(42) == 42 + assert m.test_nullopt(43) == 43 + + assert m.test_no_assign() == 42 + assert m.test_no_assign(None) == 42 + assert m.test_no_assign(m.NoAssign(43)) == 43 + pytest.raises(TypeError, m.test_no_assign, 43) + + assert m.nodefer_none_optional(None) + + +@pytest.mark.skipif(not hasattr(m, "has_exp_optional"), reason='no ') +def test_exp_optional(): + assert m.double_or_zero_exp(None) == 0 + assert m.double_or_zero_exp(42) == 84 + pytest.raises(TypeError, m.double_or_zero_exp, 'foo') + + assert m.half_or_none_exp(0) is None + assert m.half_or_none_exp(42) == 21 + pytest.raises(TypeError, m.half_or_none_exp, 'foo') + + assert m.test_nullopt_exp() == 42 + assert m.test_nullopt_exp(None) == 42 + assert m.test_nullopt_exp(42) == 42 + assert m.test_nullopt_exp(43) == 43 + + assert m.test_no_assign_exp() == 42 + assert m.test_no_assign_exp(None) == 42 + assert m.test_no_assign_exp(m.NoAssign(43)) == 43 + pytest.raises(TypeError, m.test_no_assign_exp, 43) + + +@pytest.mark.skipif(not hasattr(m, "load_variant"), reason='no ') +def test_variant(doc): + assert m.load_variant(1) == "int" + assert m.load_variant("1") == "std::string" + assert m.load_variant(1.0) == "double" + assert m.load_variant(None) == "std::nullptr_t" + + assert m.load_variant_2pass(1) == "int" + assert m.load_variant_2pass(1.0) == "double" + + assert m.cast_variant() == (5, "Hello") + + assert doc(m.load_variant) == "load_variant(arg0: Union[int, str, float, None]) -> str" + From 00d0b00c52f47edf36e46b776005fcc5b94fb571 Mon Sep 17 00:00:00 2001 From: Giovanni Funchal Date: Fri, 8 Dec 2017 14:16:28 +0000 Subject: [PATCH 2/3] Fix code style in python tests --- tests/test_stl.py | 9 +++++---- tests/test_utility.py | 2 -- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/tests/test_stl.py b/tests/test_stl.py index d6324908e9..9f1396218e 100644 --- a/tests/test_stl.py +++ b/tests/test_stl.py @@ -127,10 +127,11 @@ def test_missing_header_message(): should result in a helpful suggestion in the error message""" import pybind11_cross_module_tests as cm - expected_message = ("Did you forget to `#include `? Or ,\n" - ", , , etc.\n" - "Some automatic conversions are optional and require extra headers to be\n" - "included when compiling your pybind11 module.") + expected_message = ( + "Did you forget to `#include `? Or ,\n" + ", , , etc.\n" + "Some automatic conversions are optional and require extra headers to be\n" + "included when compiling your pybind11 module.") with pytest.raises(TypeError) as excinfo: cm.missing_header_arg([1.0, 2.0, 3.0]) diff --git a/tests/test_utility.py b/tests/test_utility.py index 14c705f28b..d517731eb8 100644 --- a/tests/test_utility.py +++ b/tests/test_utility.py @@ -1,7 +1,6 @@ import pytest from pybind11_tests import utility as m -from pybind11_tests import UserType @pytest.mark.skipif(not hasattr(m, "has_optional"), reason='no ') @@ -61,4 +60,3 @@ def test_variant(doc): assert m.cast_variant() == (5, "Hello") assert doc(m.load_variant) == "load_variant(arg0: Union[int, str, float, None]) -> str" - From 02af14dfa58b20db77b3a53b179c8e3dc222ae92 Mon Sep 17 00:00:00 2001 From: Giovanni Funchal Date: Fri, 8 Dec 2017 14:19:52 +0000 Subject: [PATCH 3/3] Fix changelog syntax --- docs/changelog.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index bbfdf28329..ae82e6db9d 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -21,9 +21,9 @@ v2.3.0 (Not yet released) * The ``value()`` method of ``py::enum_`` now accepts an optional docstring that will be shown in the documentation of the associated enumeration. -* Added `pybind11/utility.h`, which enables support for STL utility types - (`std::optional` and `std::variant`) separately from STL containers. - `pybind11/stl.h` enables both. +* Added ``pybind11/utility.h``, which enables support for STL utility types + (``std::optional`` and ``std::variant``) separately from STL containers. + ``pybind11/stl.h`` enables both. `#1207 `_. v2.2.1 (September 14, 2017)