Skip to content

Commit 7918bcc

Browse files
committed
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.
1 parent e06077b commit 7918bcc

File tree

6 files changed

+72
-28
lines changed

6 files changed

+72
-28
lines changed

.appveyor.yml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,12 @@ matrix:
2828
install:
2929
- ps: |
3030
if ($env:PLATFORM -eq "x64") { $env:CMAKE_ARCH = "x64" }
31-
if ($env:APPVEYOR_JOB_NAME -like "*Visual Studio 2017*") { $env:CMAKE_GENERATOR = "Visual Studio 15 2017" }
32-
else { $env:CMAKE_GENERATOR = "Visual Studio 14 2015" }
31+
if ($env:APPVEYOR_JOB_NAME -like "*Visual Studio 2017*") {
32+
$env:CMAKE_GENERATOR = "Visual Studio 15 2017"
33+
$env:CMAKE_INCLUDE_PATH = "C:\Libraries\boost_1_64_0"
34+
} else {
35+
$env:CMAKE_GENERATOR = "Visual Studio 14 2015"
36+
}
3337
if ($env:PYTHON) {
3438
if ($env:PLATFORM -eq "x64") { $env:PYTHON = "$env:PYTHON-x64" }
3539
$env:PATH = "C:\Python$env:PYTHON\;C:\Python$env:PYTHON\Scripts\;$env:PATH"
@@ -45,7 +49,7 @@ install:
4549
- ps: |
4650
Start-FileDownload 'http://bitbucket.org/eigen/eigen/get/3.3.3.zip'
4751
7z x 3.3.3.zip -y > $null
48-
$env:CMAKE_INCLUDE_PATH = "eigen-eigen-67e894c6cd8f"
52+
$env:CMAKE_INCLUDE_PATH = "eigen-eigen-67e894c6cd8f;$env:CMAKE_INCLUDE_PATH"
4953
build_script:
5054
- cmake -G "%CMAKE_GENERATOR%" -A "%CMAKE_ARCH%"
5155
-DPYBIND11_CPP_STANDARD=/std:c++%CPP%

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ install:
150150
$SCRIPT_RUN_PREFIX sh -c "for s in 0 15; do sleep \$s; \
151151
apt-get -qy --no-install-recommends install \
152152
$PY_DEBUG python$PYTHON-dev python$PY-pytest python$PY-scipy \
153-
libeigen3-dev cmake make ${COMPILER_PACKAGES} && break; done"
153+
libeigen3-dev libboost-dev cmake make ${COMPILER_PACKAGES} && break; done"
154154
else
155155
156156
if [ "$CLANG" = "4.0" ]; then

docs/advanced/cast/stl.rst

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,7 @@ for custom variant types:
6060
template <>
6161
struct visit_helper<boost::variant> {
6262
template <typename... Args>
63-
static auto call(Args &&...args)
64-
-> decltype(boost::apply_visitor(args...)) {
63+
static auto call(Args &&...args) -> decltype(boost::apply_visitor(args...)) {
6564
return boost::apply_visitor(args...);
6665
}
6766
};
@@ -71,6 +70,13 @@ The ``visit_helper`` specialization is not required if your ``name::variant`` pr
7170
a ``name::visit()`` function. For any other function name, the specialization must be
7271
included to tell pybind11 how to visit the variant.
7372

73+
.. note::
74+
75+
pybind11 only supports the modern implementation of ``boost::variant``
76+
which makes use of variadic templates. This requires Boost 1.56 or newer.
77+
Additionally, on Windows, MSVC 2017 is required because ``boost::variant``
78+
falls back to the old non-variadic implementation on MSVC 2015.
79+
7480
.. _opaque:
7581

7682
Making opaque types

include/pybind11/stl.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,8 +292,10 @@ struct variant_caster_visitor {
292292
return_value_policy policy;
293293
handle parent;
294294

295+
using result_type = handle; // required by boost::variant in C++11
296+
295297
template <typename T>
296-
handle operator()(T &&src) const {
298+
result_type operator()(T &&src) const {
297299
return make_caster<T>::cast(std::forward<T>(src), policy, parent);
298300
}
299301
};

tests/CMakeLists.txt

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,9 @@ if(PYBIND11_TEST_FILES_EIGEN_I GREATER -1)
112112
endif()
113113
endif()
114114

115+
# Optional dependency for some tests
116+
find_package(Boost)
117+
115118
# Compile with compiler warnings turned on
116119
function(pybind11_enable_warnings target_name)
117120
if(MSVC)
@@ -141,37 +144,40 @@ foreach(t ${PYBIND11_CROSS_MODULE_TESTS})
141144
endforeach()
142145

143146
set(testdir ${CMAKE_CURRENT_SOURCE_DIR})
144-
foreach(tgt ${test_targets})
147+
foreach(target ${test_targets})
145148
set(test_files ${PYBIND11_TEST_FILES})
146-
if(NOT tgt STREQUAL "pybind11_tests")
149+
if(NOT target STREQUAL "pybind11_tests")
147150
set(test_files "")
148151
endif()
149152

150153
# Create the binding library
151-
pybind11_add_module(${tgt} THIN_LTO ${tgt}.cpp
152-
${test_files} ${PYBIND11_HEADERS})
153-
154-
pybind11_enable_warnings(${tgt})
154+
pybind11_add_module(${target} THIN_LTO ${target}.cpp ${test_files} ${PYBIND11_HEADERS})
155+
pybind11_enable_warnings(${target})
155156

156157
if(MSVC)
157-
target_compile_options(${tgt} PRIVATE /utf-8)
158+
target_compile_options(${target} PRIVATE /utf-8)
158159
endif()
159160

160161
if(EIGEN3_FOUND)
161162
if (PYBIND11_EIGEN_VIA_TARGET)
162-
target_link_libraries(${tgt} PRIVATE Eigen3::Eigen)
163+
target_link_libraries(${target} PRIVATE Eigen3::Eigen)
163164
else()
164-
target_include_directories(${tgt} PRIVATE ${EIGEN3_INCLUDE_DIR})
165+
target_include_directories(${target} PRIVATE ${EIGEN3_INCLUDE_DIR})
165166
endif()
166-
target_compile_definitions(${tgt} PRIVATE -DPYBIND11_TEST_EIGEN)
167+
target_compile_definitions(${target} PRIVATE -DPYBIND11_TEST_EIGEN)
168+
endif()
169+
170+
if(Boost_FOUND)
171+
target_include_directories(${target} PRIVATE ${Boost_INCLUDE_DIRS})
172+
target_compile_definitions(${target} PRIVATE -DPYBIND11_TEST_BOOST)
167173
endif()
168174

169175
# Always write the output file directly into the 'tests' directory (even on MSVC)
170176
if(NOT CMAKE_LIBRARY_OUTPUT_DIRECTORY)
171-
set_target_properties(${tgt} PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${testdir})
177+
set_target_properties(${target} PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${testdir})
172178
foreach(config ${CMAKE_CONFIGURATION_TYPES})
173179
string(TOUPPER ${config} config)
174-
set_target_properties(${tgt} PROPERTIES LIBRARY_OUTPUT_DIRECTORY_${config} ${testdir})
180+
set_target_properties(${target} PROPERTIES LIBRARY_OUTPUT_DIRECTORY_${config} ${testdir})
175181
endforeach()
176182
endif()
177183
endforeach()

tests/test_stl.cpp

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,27 @@
1010
#include "pybind11_tests.h"
1111
#include <pybind11/stl.h>
1212

13+
// Test with `std::variant` in C++17 mode, or with `boost::variant` in C++11/14
14+
#if PYBIND11_HAS_VARIANT
15+
using std::variant;
16+
#elif PYBIND11_TEST_BOOST
17+
# include <boost/variant.hpp>
18+
# define PYBIND11_HAS_VARIANT 1
19+
using boost::variant;
20+
21+
namespace pybind11 { namespace detail {
22+
template <typename... Ts>
23+
struct type_caster<boost::variant<Ts...>> : variant_caster<boost::variant<Ts...>> {};
24+
25+
template <>
26+
struct visit_helper<boost::variant> {
27+
template <typename... Args>
28+
static auto call(Args &&...args) -> decltype(boost::apply_visitor(args...)) {
29+
return boost::apply_visitor(args...);
30+
}
31+
};
32+
}} // namespace pybind11::detail
33+
#endif
1334

1435
/// Issue #528: templated constructor
1536
struct TplCtorClass {
@@ -162,22 +183,27 @@ TEST_SUBMODULE(stl, m) {
162183
#endif
163184

164185
#ifdef PYBIND11_HAS_VARIANT
186+
static_assert(std::is_same<py::detail::variant_caster_visitor::result_type, py::handle>::value,
187+
"visitor::result_type is required by boost::variant in C++11 mode");
188+
165189
struct visitor {
166-
const char *operator()(int) { return "int"; }
167-
const char *operator()(std::string) { return "std::string"; }
168-
const char *operator()(double) { return "double"; }
169-
const char *operator()(std::nullptr_t) { return "std::nullptr_t"; }
190+
using result_type = const char *;
191+
192+
result_type operator()(int) { return "int"; }
193+
result_type operator()(std::string) { return "std::string"; }
194+
result_type operator()(double) { return "double"; }
195+
result_type operator()(std::nullptr_t) { return "std::nullptr_t"; }
170196
};
171197

172198
// test_variant
173-
m.def("load_variant", [](std::variant<int, std::string, double, std::nullptr_t> v) {
174-
return std::visit(visitor(), v);
199+
m.def("load_variant", [](variant<int, std::string, double, std::nullptr_t> v) {
200+
return py::detail::visit_helper<variant>::call(visitor(), v);
175201
});
176-
m.def("load_variant_2pass", [](std::variant<double, int> v) {
177-
return std::visit(visitor(), v);
202+
m.def("load_variant_2pass", [](variant<double, int> v) {
203+
return py::detail::visit_helper<variant>::call(visitor(), v);
178204
});
179205
m.def("cast_variant", []() {
180-
using V = std::variant<int, std::string>;
206+
using V = variant<int, std::string>;
181207
return py::make_tuple(V(5), V("Hello"));
182208
});
183209
#endif

0 commit comments

Comments
 (0)