-
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
Introduce recursive_container_traits #4623
Changes from 13 commits
541c360
2c6002c
4c59860
2c48ca9
8d35d80
79782b5
ba0aadf
0ecf5f0
9ba38bf
531ceb4
1bd1e7e
a839f8f
eba6239
77e2c56
3ed36b8
71521c4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -822,14 +822,70 @@ using movable_cast_op_type | |
typename std::add_rvalue_reference<intrinsic_t<T>>::type, | ||
typename std::add_lvalue_reference<intrinsic_t<T>>::type>>; | ||
|
||
// True if Container has a dependent type mapped_type that is equivalent | ||
// to Container itself | ||
// Actual implementation in the SFINAE specialization below | ||
template <typename Container, typename MappedType = Container> | ||
struct is_container_with_recursive_mapped_type : std::false_type {}; | ||
|
||
// This specialization is only valid if both conditions are fulfilled: | ||
// 1) The mapped_type exists | ||
// 2) And it is equivalent to Container | ||
template <typename Container> | ||
struct is_container_with_recursive_mapped_type<Container, typename Container::mapped_type> | ||
: std::true_type {}; | ||
|
||
// True if Container has a dependent type value_type that is equivalent | ||
// to Container itself | ||
// Actual implementation in the SFINAE specialization below | ||
template <typename Container, typename MappedType = Container> | ||
struct is_container_with_recursive_value_type : std::false_type {}; | ||
|
||
// This specialization is only valid if both conditions are fulfilled: | ||
// 1) The value_type exists | ||
// 2) And it is equivalent to Container | ||
template <typename Container> | ||
struct is_container_with_recursive_value_type<Container, typename Container::value_type> | ||
: std::true_type {}; | ||
|
||
// True constant if the type contains itself recursively. | ||
// By default, this will check the mapped_type and value_type dependent types. | ||
// In more complex recursion patterns, users can specialize this struct. | ||
// The second template parameter SFINAE=void is for use of std::enable_if in specializations. | ||
// An example is found in tests/test_stl_binders.cpp. | ||
template <typename Container, typename SFINAE = void> | ||
struct is_recursive_container : any_of<is_container_with_recursive_value_type<Container>, | ||
is_container_with_recursive_mapped_type<Container>> {}; | ||
|
||
template <typename T, typename SFINAE = void> | ||
struct is_move_constructible : std::is_move_constructible<T> {}; | ||
|
||
// Specialization for types that appear to be move constructible but also look like stl containers | ||
// (we specifically check for: has `value_type` and `reference` with `reference = value_type&`): if | ||
// so, move constructability depends on whether the value_type is move constructible. | ||
template <typename Container> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
We don't want SFINAE to stop here, users may still need There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, but I've now done it in such a way that the SFINAE parameter of the publically specializable struct defaults to void as is usual for SFINAE. |
||
struct is_move_constructible< | ||
Container, | ||
enable_if_t< | ||
all_of<std::is_move_constructible<Container>, | ||
std::is_same<typename Container::value_type &, typename Container::reference>, | ||
// Avoid infinite recursion | ||
negation<is_recursive_container<Container>>>::value>> | ||
: is_move_constructible<typename Container::value_type> {}; | ||
|
||
// Likewise for std::pair | ||
// (after C++17 it is mandatory that the move constructor not exist when the two types aren't | ||
// themselves move constructible, but this can not be relied upon when T1 or T2 are themselves | ||
// containers). | ||
template <typename T1, typename T2> | ||
struct is_move_constructible<std::pair<T1, T2>> | ||
: all_of<is_move_constructible<T1>, is_move_constructible<T2>> {}; | ||
|
||
// std::is_copy_constructible isn't quite enough: it lets std::vector<T> (and similar) through when | ||
// T is non-copyable, but code containing such a copy constructor fails to actually compile. | ||
template <typename T, typename SFINAE = void> | ||
struct is_copy_constructible : std::is_copy_constructible<T> {}; | ||
|
||
template <typename T, typename SFINAE = void> | ||
struct is_move_constructible : std::is_move_constructible<T> {}; | ||
|
||
// Specialization for types that appear to be copy constructible but also look like stl containers | ||
// (we specifically check for: has `value_type` and `reference` with `reference = value_type&`): if | ||
// so, copy constructability depends on whether the value_type is copy constructible. | ||
|
@@ -840,7 +896,7 @@ struct is_copy_constructible< | |
all_of<std::is_copy_constructible<Container>, | ||
std::is_same<typename Container::value_type &, typename Container::reference>, | ||
// Avoid infinite recursion | ||
negation<std::is_same<Container, typename Container::value_type>>>::value>> | ||
negation<is_recursive_container<Container>>>::value>> | ||
: is_copy_constructible<typename Container::value_type> {}; | ||
|
||
// Likewise for std::pair | ||
|
@@ -855,14 +911,21 @@ struct is_copy_constructible<std::pair<T1, T2>> | |
template <typename T, typename SFINAE = void> | ||
struct is_copy_assignable : std::is_copy_assignable<T> {}; | ||
template <typename Container> | ||
struct is_copy_assignable<Container, | ||
enable_if_t<all_of<std::is_copy_assignable<Container>, | ||
std::is_same<typename Container::value_type &, | ||
typename Container::reference>>::value>> | ||
struct is_copy_assignable< | ||
Container, | ||
enable_if_t< | ||
all_of<std::is_copy_assignable<Container>, | ||
std::is_same<typename Container::value_type &, typename Container::reference>, | ||
// Avoid infinite recursion | ||
negation<is_recursive_container<Container>>>::value>> | ||
: is_copy_assignable<typename Container::value_type> {}; | ||
template <typename T1, typename T2> | ||
struct is_copy_assignable<std::pair<T1, T2>> | ||
: all_of<is_copy_assignable<T1>, is_copy_assignable<T2>> {}; | ||
/* | ||
* Need to remove the const qualifier from T1 here since the value_type in | ||
* STL map types is std::pair<const Key, T>, and const types are never assignable | ||
*/ | ||
: all_of<is_copy_assignable<typename std::remove_const<T1>::type>, is_copy_assignable<T2>> {}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The asymmetry here seems odd, did you already think this through? Concretely, would it make sense to apply Because for Which leads me to think: It has to be either none or both. Or we actually have a trait with subtly different semantics compared to the "pure" There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed on this point, the asymmetry is a bit odd. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Fixing this required changing the logic of this PR quite a bit. /*
* Tag to be used for representing the bottom of recursively defined types.
* Define this tag so we don't have to use void.
*/
struct recursive_bottom {};
template <typename Container, typename SFINAE = void>
struct impl_recursive_container_traits {
using type_to_check_recursively = recursive_bottom;
};
// and its main logic in the specialization The
Having this trait makes the definitions of custom traits such as template <typename T>
struct is_move_constructible
: all_of<std::is_move_constructible<T>,
is_move_constructible<
typename recursive_container_traits<T>::type_to_check_recursively>> {};
template <>
struct is_move_constructible<recursive_bottom> : std::true_type {};
// Likewise for std::pair
// (after C++17 it is mandatory that the move constructor not exist when the two types aren't
// themselves move constructible, but this can not be relied upon when T1 or T2 are themselves
// containers).
template <typename T1, typename T2>
struct is_move_constructible<std::pair<T1, T2>>
: all_of<is_move_constructible<T1>, is_move_constructible<T2>> {}; There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Oh, wow. Thanks a lot for going the extra mile! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've fixed some comments now, should be ready for review :) |
||
|
||
PYBIND11_NAMESPACE_END(detail) | ||
|
||
|
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.
Could we generalize this and at the same time make this more uniform?
But a silly thing first: the naming here completely throws me off. It kind-of makes sense with my mind in the context (rare occasion), but imaging myself skimming through this while debugging or working on something completely different (the usual situation), my snap-judgement of what this is doing would go in a completely wrong direction.
I'll use
is_container_with_self_referential_mapped_type
below.Currently we have two special cases:
Could we turn this into
that
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 have done this and I like the way that it turned out, especially since adding custom user specializations now involves overriding a single struct, which just tells Pybind that this type is recursive: