Skip to content

Improve support for std::optional-like classes #850

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from

Conversation

bmerry
Copy link
Contributor

@bmerry bmerry commented May 14, 2017

With older versions of Boost, value = {} can fail to compile because
the interpretation of {} is ambiguous (could construct boost::none
or the value type). It can also fail with std::experimental::optional,
at least on GCC 5.4, because it would construct an instance of the
optional type and then move-assign it, and if the value type isn't move
assignable this would fail.

This is replaced by preferring .reset if the type supports it, and
also providing a traits class that can be extended to override the
behaviour where necessary (which is done for
std::experimental::optional).

Additionally, the assignment of the value from the inner caster was by
copy rather than move, which prevented use with uncopyable types.

Closes #847.

With older versions of Boost, `value = {}` can fail to compile because
the interpretation of `{}` is ambiguous (could construct `boost::none`
or the value type). It can also fail with `std::experimental::optional`,
at least on GCC 5.4, because it would construct an instance of the
optional type and then move-assign it, and if the value type isn't move
assignable this would fail.

This is replaced by preferring `.reset` if the type supports it, and
also providing a traits class that can be extended to override the
behaviour where necessary (which is done for
std::experimental::optional).

Additionally, the assignment of the value from the inner caster was by
copy rather than move, which prevented use with uncopyable types.

Closes pybind#847.
return true;
}
value_conv inner_caster;
if (!inner_caster.load(src, convert))
return false;

value.emplace(cast_op<typename T::value_type>(inner_caster));
value.emplace(std::move(cast_op<typename T::value_type>(inner_caster)));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can someone sanity-check me that this is safe? I don't have an understanding of how all the casting magic works yet.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not safe: type casters can return a lvalue reference to a variable they don't own. We might, however, be able to coax type casters cast ops in rvalue context into giving up an rvalue reference.

It was moving the output of a caster, which is not safe because a caster
can return an lvalue reference which it doesn't own. The wrapped type
must now be copy-constructible, but does not need to be either move- or
copy-assignable.
@jagerman
Copy link
Member

Does the .reset() raise any deprecation warnings with boost::optional? I'm guessing that boost is going to follow that path, rather than the = {}; path because it exists, even if it is deprecated.

If it does, is it possible/would it be better to SFINAE on = {}; being supported, rather than .reset()? I'm not sure what would happen with the previous boost version ambiguity -- would it be SFINAE-declined because it's ambiguous?

@bmerry
Copy link
Contributor Author

bmerry commented May 15, 2017

I haven't seen any deprecated warnings from boost::optional, at least with version 1.58.

My main reason for making the .reset() path the default is that this way the path should only depend on the optional class, and not on the type T that it wraps. If the = {} path was default then with (older) boost::optional it would take that path for some T (where it was unambiguous) and the .reset() path in others, didn't feel right. For a well-tested class like boost::optional it's probably fine, but if someone provided their own optional class it might expose weird behaviour.

@bmerry
Copy link
Contributor Author

bmerry commented May 15, 2017

I've also submitted https://svn.boost.org/trac/boost/ticket/13031 to suggest un-deprecating reset.

@dean0x7d
Copy link
Member

AFAIK casters are generally only used once, except for some subcasters for vectors, maps, etc. But it looks like #851 makes those single-use-only casters as well. So if all caster are only ever used once, a possible simple solution would be to remove value = {} completely and just return true -- the default constructed optional in the caster is already empty.

@jagerman
Copy link
Member

You might also report via https://github.com/boostorg/optional/issues

// both exist then the .reset version is preferred due to a better match
// in the second argument.
template<typename T2 = T>
static decltype(std::declval<T2>().reset()) reset_impl(T &value, int) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

static void_t<decltype(std::declval<T2>().reset())> reset_impl... would be better here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And return value.reset(); -> value.reset();

}

template<typename T2 = T>
static decltype(std::declval<T2>() = {}) reset_impl(T &value, long) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... and here

@dean0x7d
Copy link
Member

Any thoughts on dispensing with reset altogether and simply replacing:

if (src.is_none()) {
    value = {};  // nullopt
    return true;
}

with:

if (src.is_none()) {
    return true; // default constructed `value` is already `nullopt`
}

@jagerman
Copy link
Member

I think, with #851 now merged, there isn't any multiple-load() casting code left, so it would probably work now.

@bmerry
Copy link
Contributor Author

bmerry commented May 26, 2017

Any thoughts on dispensing with reset altogether and simply replacing:

It would certainly reduce complexity, if you're sure that all casters are single-use.

@dean0x7d
Copy link
Member

Closed in favor of #874.

@dean0x7d dean0x7d closed this May 27, 2017
@bmerry bmerry deleted the optional_reset branch May 28, 2017 10:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants