-
Notifications
You must be signed in to change notification settings - Fork 2.1k
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
tuple/pair/stl.h casters: use rvalue subcasting when casting an rvalue tuple/pair/container #936
Conversation
ae91eca
to
79d5baf
Compare
I've updated this PR to also consolidate the tuple/pair casters into a single I've also added tests. |
This looks great! Combining the pair and tuple casters is a nice simplification. |
I'm curious: this reminds me of/sounds like it is very similar to the problem of returning an vector of move-only types (cfr. #853). Could this kind of solution also help there, for |
I'm working on doing the same for |
Awesome, thanks! :-) |
@YannickJadoul - This also fixes #853 now (I used an adapted version your test case in the tests, which now passes). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The consolidation of pair/tuple is very nice! Looks great overall. The only significant suggestion I have is adding a forward_like<Container>()
function (see comments).
include/pybind11/cast.h
Outdated
@@ -1338,14 +1304,15 @@ template <typename... Tuple> class type_caster<std::tuple<Tuple...>> { | |||
return true; | |||
} | |||
|
|||
static handle cast_impl(const type &, return_value_policy, handle, | |||
template <typename T> | |||
static handle cast_impl(T &&, return_value_policy, handle, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since it's empty, it should be fine to leave this one as const types &
and avoid a template instantiation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With just the const type &
an rvalue argument will end up at the full template version. I suppose I could put in dual empty methods (one lvalue, one rvalue) if you like that better (though there are still things like non-const lvalue and const rvalue that would slip by).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, I missed that. Never mind, it's better to leave it with T &&
then.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll try just taking it out completely (i.e. so it lands in the base case) and see if that causes any problems.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you prefer keeping it in (and templated), or taking it out entirely, as in the last commit?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 for removing it completely -- perfect!
include/pybind11/cast.h
Outdated
@@ -1312,6 +1275,10 @@ template <typename... Tuple> class type_caster<std::tuple<Tuple...>> { | |||
return cast_impl(src, policy, parent, indices{}); | |||
} | |||
|
|||
static handle cast(type &&src, return_value_policy policy, handle parent) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe merge the const&
and &&
overloads into a single function with forwarding?
include/pybind11/stl.h
Outdated
template <typename ContainerArg, typename Value> | ||
using forward_item = conditional_t< | ||
std::is_lvalue_reference<ContainerArg>::value, const Value &, | ||
typename std::add_rvalue_reference<Value>::type>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Explicitly using const Value &
is a bit less generic than it could be: remove_reference_t<Value>&
and remove_reference_t<Value>&&
.
Consider wrapping it in a forward_like
function like here: https://stackoverflow.com/a/29780197/7189436
forward_like<Container>(element)
is pretty nice and expressive.
include/pybind11/cast.h
Outdated
using indices = make_index_sequence<sizeof...(Tuple)>; | ||
// Base implementation for std::tuple and std::pair | ||
template <template<typename...> class TupleType, typename... Tuple> class tuple_caster { | ||
using type = TupleType<Tuple...>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider renaming Tuple...
into Ts...
or similar. At first glance TupleType<Tuple...>
looks like a tuple of tuples.
tests/test_stl.cpp
Outdated
@@ -61,6 +61,50 @@ TEST_SUBMODULE(stl, m) { | |||
return set.count("key1") && set.count("key2") && set.count("key3"); | |||
}); | |||
|
|||
#if defined(_MSC_VER) && _MSC_VER < 1910 | |||
// We get some really long type names here which causes MSVC 2015 to emit warnings | |||
# pragma warning(disable: 4503) // warning C4503: decorated name length exceeded, name was truncated |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd suggest adding this globally to pybind11_tests.h
since it's already required in two files and likely more in the future.
That's a nice suggestion, and it shortens up the code a bit too (by eliminating the |
The std::pair caster can be written as a special case of the std::tuple caster; this combines them via a base `tuple_caster` class (which is essentially identical to the previous std::tuple caster). This also removes the special empty tuple base case: returning an empty tuple is relatively rare, and the base case still works perfectly well even when the tuple types is an empty list.
This updates the std::tuple, std::pair and `stl.h` type casters to forward their contained value according to whether the container being cast is an lvalue or rvalue reference. This fixes an issue where subcaster casts were always called with a const lvalue which meant nested type casters didn't have the desired `cast()` overload invoked. For example, this caused Eigen values in a tuple to end up with a readonly flag (issue pybind#935) and made it impossible to return a container of move-only types (issue pybind#853). This fixes both issues by adding templated universal reference `cast()` methods to the various container types that forward container elements according to the container reference type.
c0a3f20
to
3498e79
Compare
PR pybind#936 broke the ability to return a pointer to a stl container (and, likewise, to a tuple) because the added deduced type matched a non-const pointer argument: the pointer-accepting `cast` in PYBIND11_TYPE_CASTER had a `const type *`, which is a worse match for a non-const pointer than the universal reference template pybind#936 added. This changes the provided TYPE_CASTER cast(ptr) to take the pointer by template arg (so that it will accept either const or non-const pointer). It has two other effects: it slightly reduces .so size (because many type casters never actually need the pointer cast at all), and it allows type casters to provide their untemplated pointer `cast()` that will take precedence over the templated version provided in the macro.
PR pybind#936 broke the ability to return a pointer to a stl container (and, likewise, to a tuple) because the added deduced type matched a non-const pointer argument: the pointer-accepting `cast` in PYBIND11_TYPE_CASTER had a `const type *`, which is a worse match for a non-const pointer than the universal reference template pybind#936 added. This changes the provided TYPE_CASTER cast(ptr) to take the pointer by template arg (so that it will accept either const or non-const pointer). It has two other effects: it slightly reduces .so size (because many type casters never actually need the pointer cast at all), and it allows type casters to provide their untemplated pointer `cast()` that will take precedence over the templated version provided in the macro.
PR pybind#936 broke the ability to return a pointer to a stl container (and, likewise, to a tuple) because the added deduced type matched a non-const pointer argument: the pointer-accepting `cast` in PYBIND11_TYPE_CASTER had a `const type *`, which is a worse match for a non-const pointer than the universal reference template pybind#936 added. This changes the provided TYPE_CASTER cast(ptr) to take the pointer by template arg (so that it will accept either const or non-const pointer). It has two other effects: it slightly reduces .so size (because many type casters never actually need the pointer cast at all), and it allows type casters to provide their untemplated pointer `cast()` that will take precedence over the templated version provided in the macro.
PR pybind#936 broke the ability to return a pointer to a stl container (and, likewise, to a tuple) because the added deduced type matched a non-const pointer argument: the pointer-accepting `cast` in PYBIND11_TYPE_CASTER had a `const type *`, which is a worse match for a non-const pointer than the universal reference template pybind#936 added. This changes the provided TYPE_CASTER cast(ptr) to take the pointer by template arg (so that it will accept either const or non-const pointer). It has two other effects: it slightly reduces .so size (because many type casters never actually need the pointer cast at all), and it allows type casters to provide their untemplated pointer `cast()` that will take precedence over the templated version provided in the macro.
PR #936 broke the ability to return a pointer to a stl container (and, likewise, to a tuple) because the added deduced type matched a non-const pointer argument: the pointer-accepting `cast` in PYBIND11_TYPE_CASTER had a `const type *`, which is a worse match for a non-const pointer than the universal reference template #936 added. This changes the provided TYPE_CASTER cast(ptr) to take the pointer by template arg (so that it will accept either const or non-const pointer). It has two other effects: it slightly reduces .so size (because many type casters never actually need the pointer cast at all), and it allows type casters to provide their untemplated pointer `cast()` that will take precedence over the templated version provided in the macro.
The tuple caster had only a const-lvalue-ref cast method, which meant
that subcaster casts couldn't distinguish between an lvalue and rvalue
cast; this caused Eigen values in a tuple, for instance, to end up with
a readonly flag.
This fixes it by adding an rvalue versions of
cast()
andcast_impl()
that move the tuple values when subcasting them.
Fixes #935 and #853
Edit: I've updated this PR to also consolidate the tuple/pair casters into a single
tuple_caster
implementation. It is essentially just thestd::tuple
implementation, which works perfectly well withstd::pair
s. Thus the fix here also gets applied tostd::pair
.Edit 2: Also
stl.h
casters get the same treatment, which (incidentally) also fixes #853.