Add support for boost::variant in C++11 mode
In C++11 mode, `boost::apply_visitor` requires an explicit `result_type`.
This also adds optional tests for `boost::variant` in C++11/14, if boost
is available. In C++17 mode, `std::variant` is tested instead.
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index aa2704b..18e6b9f 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -112,6 +112,9 @@
endif()
endif()
+# Optional dependency for some tests
+find_package(Boost)
+
# Compile with compiler warnings turned on
function(pybind11_enable_warnings target_name)
if(MSVC)
@@ -141,37 +144,40 @@
endforeach()
set(testdir ${CMAKE_CURRENT_SOURCE_DIR})
-foreach(tgt ${test_targets})
+foreach(target ${test_targets})
set(test_files ${PYBIND11_TEST_FILES})
- if(NOT tgt STREQUAL "pybind11_tests")
+ if(NOT target STREQUAL "pybind11_tests")
set(test_files "")
endif()
# Create the binding library
- pybind11_add_module(${tgt} THIN_LTO ${tgt}.cpp
- ${test_files} ${PYBIND11_HEADERS})
-
- pybind11_enable_warnings(${tgt})
+ pybind11_add_module(${target} THIN_LTO ${target}.cpp ${test_files} ${PYBIND11_HEADERS})
+ pybind11_enable_warnings(${target})
if(MSVC)
- target_compile_options(${tgt} PRIVATE /utf-8)
+ target_compile_options(${target} PRIVATE /utf-8)
endif()
if(EIGEN3_FOUND)
if (PYBIND11_EIGEN_VIA_TARGET)
- target_link_libraries(${tgt} PRIVATE Eigen3::Eigen)
+ target_link_libraries(${target} PRIVATE Eigen3::Eigen)
else()
- target_include_directories(${tgt} PRIVATE ${EIGEN3_INCLUDE_DIR})
+ target_include_directories(${target} PRIVATE ${EIGEN3_INCLUDE_DIR})
endif()
- target_compile_definitions(${tgt} PRIVATE -DPYBIND11_TEST_EIGEN)
+ target_compile_definitions(${target} PRIVATE -DPYBIND11_TEST_EIGEN)
+ endif()
+
+ if(Boost_FOUND)
+ target_include_directories(${target} PRIVATE ${Boost_INCLUDE_DIRS})
+ target_compile_definitions(${target} PRIVATE -DPYBIND11_TEST_BOOST)
endif()
# Always write the output file directly into the 'tests' directory (even on MSVC)
if(NOT CMAKE_LIBRARY_OUTPUT_DIRECTORY)
- set_target_properties(${tgt} PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${testdir})
+ set_target_properties(${target} PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${testdir})
foreach(config ${CMAKE_CONFIGURATION_TYPES})
string(TOUPPER ${config} config)
- set_target_properties(${tgt} PROPERTIES LIBRARY_OUTPUT_DIRECTORY_${config} ${testdir})
+ set_target_properties(${target} PROPERTIES LIBRARY_OUTPUT_DIRECTORY_${config} ${testdir})
endforeach()
endif()
endforeach()
diff --git a/tests/test_stl.cpp b/tests/test_stl.cpp
index 93e8c66..66fe9d1 100644
--- a/tests/test_stl.cpp
+++ b/tests/test_stl.cpp
@@ -10,6 +10,27 @@
#include "pybind11_tests.h"
#include <pybind11/stl.h>
+// Test with `std::variant` in C++17 mode, or with `boost::variant` in C++11/14
+#if PYBIND11_HAS_VARIANT
+using std::variant;
+#elif PYBIND11_TEST_BOOST
+# include <boost/variant.hpp>
+# define PYBIND11_HAS_VARIANT 1
+using boost::variant;
+
+namespace pybind11 { namespace detail {
+template <typename... Ts>
+struct type_caster<boost::variant<Ts...>> : variant_caster<boost::variant<Ts...>> {};
+
+template <>
+struct visit_helper<boost::variant> {
+ template <typename... Args>
+ 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 {
@@ -162,22 +183,27 @@
#endif
#ifdef PYBIND11_HAS_VARIANT
+ static_assert(std::is_same<py::detail::variant_caster_visitor::result_type, py::handle>::value,
+ "visitor::result_type is required by boost::variant in C++11 mode");
+
struct visitor {
- const char *operator()(int) { return "int"; }
- const char *operator()(std::string) { return "std::string"; }
- const char *operator()(double) { return "double"; }
- const char *operator()(std::nullptr_t) { return "std::nullptr_t"; }
+ 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", [](std::variant<int, std::string, double, std::nullptr_t> v) {
- return std::visit(visitor(), v);
+ m.def("load_variant", [](variant<int, std::string, double, std::nullptr_t> v) {
+ return py::detail::visit_helper<variant>::call(visitor(), v);
});
- m.def("load_variant_2pass", [](std::variant<double, int> v) {
- return std::visit(visitor(), v);
+ m.def("load_variant_2pass", [](variant<double, int> v) {
+ return py::detail::visit_helper<variant>::call(visitor(), v);
});
m.def("cast_variant", []() {
- using V = std::variant<int, std::string>;
+ using V = variant<int, std::string>;
return py::make_tuple(V(5), V("Hello"));
});
#endif