Avoid explicitly resetting a std::[experimental::]optional

Now that #851 has removed all multiple uses of a caster, it can just use
the default-constructed value with needing a reset. This fixes two
issues:

1. With std::experimental::optional (at least under GCC 5.4), the `= {}`
would construct an instance of the optional type and then move-assign
it, which fails if the value type isn't move-assignable.

2. With older versions of Boost, the `= {}` could fail because it is
ambiguous, allowing construction of either `boost::none` or the value
type.
This commit is contained in:
Bruce Merry 2017-05-26 08:52:54 +02:00 committed by Dean Moldovan
parent 4f9ee6e430
commit 46dbee7d42
3 changed files with 39 additions and 4 deletions

View File

@ -242,8 +242,7 @@ template<typename T> struct optional_caster {
if (!src) {
return false;
} else if (src.is_none()) {
value = {}; // nullopt
return true;
return true; // default-constructed value is already empty
}
value_conv inner_caster;
if (!inner_caster.load(src, convert))

View File

@ -188,6 +188,18 @@ struct MoveOutContainer {
struct UnregisteredType { };
// 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;
};
test_initializer python_types([](py::module &m) {
/* No constructor is explicitly defined below. An exception is raised when
trying to construct it directly from Python */
@ -236,6 +248,10 @@ test_initializer python_types([](py::module &m) {
py::print("{a} + {b} = {c}"_s.format("a"_a="py::print", "b"_a="str.format", "c"_a="this"));
});
py::class_<NoAssign>(m, "NoAssign", "Class with no C++ assignment operators")
.def(py::init<>())
.def(py::init<int>());
m.def("test_print_failure", []() { py::print(42, UnregisteredType()); });
#if !defined(NDEBUG)
m.attr("debug_enabled") = true;
@ -326,6 +342,7 @@ test_initializer python_types([](py::module &m) {
#ifdef PYBIND11_HAS_OPTIONAL
has_optional = true;
using opt_int = std::optional<int>;
using opt_no_assign = std::optional<NoAssign>;
m.def("double_or_zero", [](const opt_int& x) -> int {
return x.value_or(0) * 2;
});
@ -335,11 +352,15 @@ test_initializer python_types([](py::module &m) {
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"));
#endif
#ifdef PYBIND11_HAS_EXP_OPTIONAL
has_exp_optional = true;
using exp_opt_int = std::experimental::optional<int>;
using exp_opt_no_assign = std::experimental::optional<NoAssign>;
m.def("double_or_zero_exp", [](const exp_opt_int& x) -> int {
return x.value_or(0) * 2;
});
@ -349,6 +370,9 @@ test_initializer python_types([](py::module &m) {
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
m.attr("has_optional") = has_optional;

View File

@ -337,7 +337,8 @@ def test_accessors():
@pytest.mark.skipif(not has_optional, reason='no <optional>')
def test_optional():
from pybind11_tests import double_or_zero, half_or_none, test_nullopt
from pybind11_tests import (double_or_zero, half_or_none, test_nullopt,
test_no_assign, NoAssign)
assert double_or_zero(None) == 0
assert double_or_zero(42) == 84
@ -352,10 +353,16 @@ def test_optional():
assert test_nullopt(42) == 42
assert test_nullopt(43) == 43
assert test_no_assign() == 42
assert test_no_assign(None) == 42
assert test_no_assign(NoAssign(43)) == 43
pytest.raises(TypeError, test_no_assign, 43)
@pytest.mark.skipif(not has_exp_optional, reason='no <experimental/optional>')
def test_exp_optional():
from pybind11_tests import double_or_zero_exp, half_or_none_exp, test_nullopt_exp
from pybind11_tests import (double_or_zero_exp, half_or_none_exp, test_nullopt_exp,
test_no_assign_exp, NoAssign)
assert double_or_zero_exp(None) == 0
assert double_or_zero_exp(42) == 84
@ -370,6 +377,11 @@ def test_exp_optional():
assert test_nullopt_exp(42) == 42
assert test_nullopt_exp(43) == 43
assert test_no_assign_exp() == 42
assert test_no_assign_exp(None) == 42
assert test_no_assign_exp(NoAssign(43)) == 43
pytest.raises(TypeError, test_no_assign_exp, 43)
@pytest.mark.skipif(not hasattr(pybind11_tests, "load_variant"), reason='no <variant>')
def test_variant(doc):