diff --git a/include/pybind11/stl.h b/include/pybind11/stl.h index 8ca7fd37db..f37dd795e3 100644 --- a/include/pybind11/stl.h +++ b/include/pybind11/stl.h @@ -315,6 +315,12 @@ struct variant_caster> { 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{}); } diff --git a/tests/test_python_types.cpp b/tests/test_python_types.cpp index a82145be5e..ee006954f3 100644 --- a/tests/test_python_types.cpp +++ b/tests/test_python_types.cpp @@ -366,6 +366,10 @@ test_initializer python_types([](py::module &m) { return std::visit(visitor(), v); }); + m.def("load_variant_2pass", [](std::variant v) { + return std::visit(visitor(), v); + }); + m.def("cast_variant", []() { using V = std::variant; return py::make_tuple(V(5), V("Hello")); diff --git a/tests/test_python_types.py b/tests/test_python_types.py index 3e337d4d5b..5a3d0a1078 100644 --- a/tests/test_python_types.py +++ b/tests/test_python_types.py @@ -373,12 +373,16 @@ def test_exp_optional(): @pytest.mark.skipif(not hasattr(pybind11_tests, "load_variant"), reason='no ') def test_variant(doc): - from pybind11_tests import load_variant, cast_variant + from pybind11_tests import load_variant, load_variant_2pass, cast_variant assert load_variant(1) == "int" assert load_variant("1") == "std::string" assert load_variant(1.0) == "double" assert load_variant(None) == "std::nullptr_t" + + assert load_variant_2pass(1) == "int" + assert load_variant_2pass(1.0) == "double" + assert cast_variant() == (5, "Hello") assert doc(load_variant) == "load_variant(arg0: Union[int, str, float, None]) -> str"