Skip to content

Commit 77e2c56

Browse files
committed
More precise checking of recursive types
Instead of a trait `is_recursive_container`, use a trait `recursive_container_traits` with dependent type `recursive_container_traits::type_to_check_recursively`. So, instead of just checking if a type is recursive and then trying to somehow deal with it, recursively-defined traits such as is_move_constructible can now directly ask this trait where the recursion should proceed.
1 parent eba6239 commit 77e2c56

File tree

3 files changed

+190
-90
lines changed

3 files changed

+190
-90
lines changed

include/pybind11/detail/type_caster_base.h

Lines changed: 178 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -822,56 +822,173 @@ using movable_cast_op_type
822822
typename std::add_rvalue_reference<intrinsic_t<T>>::type,
823823
typename std::add_lvalue_reference<intrinsic_t<T>>::type>>;
824824

825-
// True if Container has a dependent type mapped_type that is equivalent
826-
// to Container itself
827-
// Actual implementation in the SFINAE specialization below
828-
template <typename Container, typename MappedType = Container>
829-
struct is_container_with_recursive_mapped_type : std::false_type {};
830-
831-
// This specialization is only valid if both conditions are fulfilled:
832-
// 1) The mapped_type exists
833-
// 2) And it is equivalent to Container
825+
template <bool Condition, typename Then, typename Else>
826+
struct if_then_else {};
827+
828+
template <typename Then, typename Else>
829+
struct if_then_else<true, Then, Else> {
830+
using type = Then;
831+
};
832+
833+
template <typename Then, typename Else>
834+
struct if_then_else<false, Then, Else> {
835+
using type = Else;
836+
};
837+
838+
// Does the container have a mapped type and is it recursive?
839+
// Implemented by specializations below.
840+
template <typename Container, typename SFINAE = void>
841+
struct container_mapped_type_traits {
842+
static constexpr bool has_mapped_type = false;
843+
static constexpr bool has_recursive_mapped_type = false;
844+
};
845+
834846
template <typename Container>
835-
struct is_container_with_recursive_mapped_type<Container, typename Container::mapped_type>
836-
: std::true_type {};
837-
838-
// True if Container has a dependent type value_type that is equivalent
839-
// to Container itself
840-
// Actual implementation in the SFINAE specialization below
841-
template <typename Container, typename MappedType = Container>
842-
struct is_container_with_recursive_value_type : std::false_type {};
843-
844-
// This specialization is only valid if both conditions are fulfilled:
845-
// 1) The value_type exists
846-
// 2) And it is equivalent to Container
847+
struct container_mapped_type_traits<
848+
Container,
849+
typename std::enable_if<
850+
std::is_same<typename Container::mapped_type, Container>::value>::type> {
851+
static constexpr bool has_mapped_type = true;
852+
static constexpr bool has_recursive_mapped_type = true;
853+
};
854+
847855
template <typename Container>
848-
struct is_container_with_recursive_value_type<Container, typename Container::value_type>
849-
: std::true_type {};
850-
851-
// True constant if the type contains itself recursively.
852-
// By default, this will check the mapped_type and value_type dependent types.
853-
// In more complex recursion patterns, users can specialize this struct.
854-
// The second template parameter SFINAE=void is for use of std::enable_if in specializations.
855-
// An example is found in tests/test_stl_binders.cpp.
856+
struct container_mapped_type_traits<
857+
Container,
858+
typename std::enable_if<
859+
negation<std::is_same<typename Container::mapped_type, Container>>::value>::type> {
860+
static constexpr bool has_mapped_type = true;
861+
static constexpr bool has_recursive_mapped_type = false;
862+
};
863+
864+
// Does the container have a value type and is it recursive?
865+
// Implemented by specializations below.
856866
template <typename Container, typename SFINAE = void>
857-
struct is_recursive_container : any_of<is_container_with_recursive_value_type<Container>,
858-
is_container_with_recursive_mapped_type<Container>> {};
867+
struct container_value_type_traits : std::false_type {
868+
static constexpr bool has_value_type = false;
869+
static constexpr bool has_recursive_value_type = false;
870+
};
859871

860-
template <typename T, typename SFINAE = void>
861-
struct is_move_constructible : std::is_move_constructible<T> {};
872+
template <typename Container>
873+
struct container_value_type_traits<
874+
Container,
875+
typename std::enable_if<
876+
std::is_same<typename Container::value_type, Container>::value>::type> {
877+
static constexpr bool has_value_type = true;
878+
static constexpr bool has_recursive_value_type = true;
879+
};
862880

863-
// Specialization for types that appear to be move constructible but also look like stl containers
864-
// (we specifically check for: has `value_type` and `reference` with `reference = value_type&`): if
865-
// so, move constructability depends on whether the value_type is move constructible.
866881
template <typename Container>
867-
struct is_move_constructible<
882+
struct container_value_type_traits<
868883
Container,
869-
enable_if_t<
870-
all_of<std::is_move_constructible<Container>,
871-
std::is_same<typename Container::value_type &, typename Container::reference>,
872-
// Avoid infinite recursion
873-
negation<is_recursive_container<Container>>>::value>>
874-
: is_move_constructible<typename Container::value_type> {};
884+
typename std::enable_if<
885+
negation<std::is_same<typename Container::value_type, Container>>::value>::type> {
886+
static constexpr bool has_value_type = true;
887+
static constexpr bool has_recursive_value_type = false;
888+
};
889+
890+
/*
891+
* Tag to be used for representing the bottom of recursively defined types.
892+
* Define this tag so we don't have to use void.
893+
*/
894+
struct recursive_bottom {};
895+
896+
/*
897+
* Implementation detail of `recursive_container_traits` below.
898+
* `T` is the `value_type` of the container, which might need to be modified to
899+
* avoid recursive types and const types.
900+
*/
901+
template <typename T, bool is_this_a_map>
902+
struct impl_type_to_check_recursively {
903+
/*
904+
* If the container is recursive, then no further recursion should be done.
905+
*/
906+
using if_recursive = recursive_bottom;
907+
/*
908+
* Otherwise yield `T` unchanged.
909+
*/
910+
using if_not_recursive = T;
911+
};
912+
913+
/*
914+
* For pairs - only as value type of a map -, the first type should remove the `const`.
915+
* Also, if the map is recursive, then the recursive checking should consider
916+
* the first type only.
917+
*/
918+
template <typename A, typename B>
919+
struct impl_type_to_check_recursively<std::pair<A, B>, /* is_this_a_map = */ true> {
920+
using if_recursive = typename std::remove_const<A>::type;
921+
using if_not_recursive = std::pair<typename std::remove_const<A>::type, B>;
922+
};
923+
924+
/*
925+
* Implementation of `recursive_container_traits` below.
926+
*/
927+
template <typename Container, typename SFINAE = void>
928+
struct impl_recursive_container_traits {
929+
using type_to_check_recursively = recursive_bottom;
930+
};
931+
932+
template <typename Container>
933+
struct impl_recursive_container_traits<
934+
Container,
935+
typename std::enable_if<container_value_type_traits<Container>::has_value_type>::type> {
936+
static constexpr bool is_recursive
937+
= container_mapped_type_traits<Container>::has_recursive_mapped_type
938+
|| container_value_type_traits<Container>::has_recursive_value_type;
939+
/*
940+
* This member dictates which type Pybind11 should check recursively in traits
941+
* such as `is_move_constructible`, `is_copy_constructible`, `is_move_assignable`, ...
942+
* Direct access to `value_type` should be avoided:
943+
* 1. `value_type` might recursively contain the type again
944+
* 2. `value_type` of STL map types is `std::pair<A const, B>`, the `const`
945+
* should be removed.
946+
*
947+
*/
948+
using type_to_check_recursively = typename if_then_else<
949+
is_recursive,
950+
typename impl_type_to_check_recursively<
951+
typename Container::value_type,
952+
container_mapped_type_traits<Container>::has_mapped_type>::if_recursive,
953+
typename impl_type_to_check_recursively<
954+
typename Container::value_type,
955+
container_mapped_type_traits<Container>::has_mapped_type>::if_not_recursive>::type;
956+
};
957+
958+
/*
959+
* This trait defines the `type_to_check_recursively` which is needed to properly
960+
* handle recursively defined traits such as `is_move_constructible` without going
961+
* into an infinite recursion.
962+
* Should be used instead of directly accessing the `value_type`.
963+
* It cancels the recursion by returning the `recursive_bottom` tag.
964+
*
965+
* The default definition of `type_to_check_recursively` is as follows:
966+
*
967+
* 1. By default, it is `recursive_bottom`, so that the recursion is canceled.
968+
* 2. If the type is non-recursive and defines a `value_type`, then the `value_type` is used.
969+
* If the `value_type` is a pair and a `mapped_type` is defined,
970+
* then the `const` is removed from the first type.
971+
* 3. If the type is recursive and `value_type` is not a pair, then `recursive_bottom` is returned.
972+
* 4. If the type is recursive and `value_type` is a pair and a `mapped_type` is defined,
973+
* then `const` is removed from the first type and the first type is returned.
974+
*
975+
* This behavior can be extended by the user as seen in test_stl_binders.cpp.
976+
*
977+
* This struct is exactly the same as impl_recursive_container_traits.
978+
* The duplication achieves that user-defined specializations don't compete
979+
* with internal specializations, but take precedence.
980+
*/
981+
template <typename Container, typename SFINAE = void>
982+
struct recursive_container_traits : impl_recursive_container_traits<Container> {};
983+
984+
template <typename T>
985+
struct is_move_constructible
986+
: all_of<std::is_move_constructible<T>,
987+
is_move_constructible<
988+
typename recursive_container_traits<T>::type_to_check_recursively>> {};
989+
990+
template <>
991+
struct is_move_constructible<recursive_bottom> : std::true_type {};
875992

876993
// Likewise for std::pair
877994
// (after C++17 it is mandatory that the move constructor not exist when the two types aren't
@@ -883,21 +1000,14 @@ struct is_move_constructible<std::pair<T1, T2>>
8831000

8841001
// std::is_copy_constructible isn't quite enough: it lets std::vector<T> (and similar) through when
8851002
// T is non-copyable, but code containing such a copy constructor fails to actually compile.
886-
template <typename T, typename SFINAE = void>
887-
struct is_copy_constructible : std::is_copy_constructible<T> {};
1003+
template <typename T>
1004+
struct is_copy_constructible
1005+
: all_of<std::is_copy_constructible<T>,
1006+
is_copy_constructible<
1007+
typename recursive_container_traits<T>::type_to_check_recursively>> {};
8881008

889-
// Specialization for types that appear to be copy constructible but also look like stl containers
890-
// (we specifically check for: has `value_type` and `reference` with `reference = value_type&`): if
891-
// so, copy constructability depends on whether the value_type is copy constructible.
892-
template <typename Container>
893-
struct is_copy_constructible<
894-
Container,
895-
enable_if_t<
896-
all_of<std::is_copy_constructible<Container>,
897-
std::is_same<typename Container::value_type &, typename Container::reference>,
898-
// Avoid infinite recursion
899-
negation<is_recursive_container<Container>>>::value>>
900-
: is_copy_constructible<typename Container::value_type> {};
1009+
template <>
1010+
struct is_copy_constructible<recursive_bottom> : std::true_type {};
9011011

9021012
// Likewise for std::pair
9031013
// (after C++17 it is mandatory that the copy constructor not exist when the two types aren't
@@ -908,24 +1018,19 @@ struct is_copy_constructible<std::pair<T1, T2>>
9081018
: all_of<is_copy_constructible<T1>, is_copy_constructible<T2>> {};
9091019

9101020
// The same problems arise with std::is_copy_assignable, so we use the same workaround.
911-
template <typename T, typename SFINAE = void>
912-
struct is_copy_assignable : std::is_copy_assignable<T> {};
913-
template <typename Container>
914-
struct is_copy_assignable<
915-
Container,
916-
enable_if_t<
917-
all_of<std::is_copy_assignable<Container>,
918-
std::is_same<typename Container::value_type &, typename Container::reference>,
919-
// Avoid infinite recursion
920-
negation<is_recursive_container<Container>>>::value>>
921-
: is_copy_assignable<typename Container::value_type> {};
1021+
template <typename T>
1022+
struct is_copy_assignable
1023+
: all_of<
1024+
std::is_copy_assignable<T>,
1025+
is_copy_assignable<typename recursive_container_traits<T>::type_to_check_recursively>> {
1026+
};
1027+
1028+
template <>
1029+
struct is_copy_assignable<recursive_bottom> : std::true_type {};
1030+
9221031
template <typename T1, typename T2>
9231032
struct is_copy_assignable<std::pair<T1, T2>>
924-
/*
925-
* Need to remove the const qualifier from T1 here since the value_type in
926-
* STL map types is std::pair<const Key, T>, and const types are never assignable
927-
*/
928-
: all_of<is_copy_assignable<typename std::remove_const<T1>::type>, is_copy_assignable<T2>> {};
1033+
: all_of<is_copy_assignable<T1>, is_copy_assignable<T2>> {};
9291034

9301035
PYBIND11_NAMESPACE_END(detail)
9311036

include/pybind11/stl_bind.h

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -61,21 +61,11 @@ struct is_comparable<
6161
/* For a vector/map data structure, recursively check the value type
6262
(which is std::pair for maps) */
6363
template <typename T>
64-
struct is_comparable<T,
65-
enable_if_t<container_traits<T>::is_vector &&
66-
// Avoid this instantiation if the type is recursive
67-
negation<is_recursive_container<T>>::value>> {
68-
static constexpr const bool value = is_comparable<typename T::value_type>::value;
69-
};
64+
struct is_comparable<T, enable_if_t<container_traits<T>::is_vector>>
65+
: is_comparable<typename recursive_container_traits<T>::type_to_check_recursively> {};
7066

71-
/* Skip the recursion if the type itself is recursive */
72-
template <typename T>
73-
struct is_comparable<T,
74-
enable_if_t<container_traits<T>::is_vector &&
75-
// Special case: The vector type is recursive
76-
is_recursive_container<T>::value>> {
77-
static constexpr const bool value = container_traits<T>::is_comparable;
78-
};
67+
template <>
68+
struct is_comparable<recursive_bottom> : std::true_type {};
7969

8070
/* For pairs, recursively check the two data types */
8171
template <typename T>

tests/test_stl_binders.cpp

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include <pybind11/numpy.h>
1111
#include <pybind11/stl_bind.h>
1212

13+
#include "pybind11/detail/type_caster_base.h"
1314
#include "pybind11_tests.h"
1415

1516
#include <deque>
@@ -86,7 +87,7 @@ struct RecursiveMap : std::map<int, RecursiveMap> {
8687
/*
8788
* Pybind11 does not catch more complicated recursion schemes, such as mutual
8889
* recursion.
89-
* In that case custom is_recursive_container specializations need to be added,
90+
* In that case custom recursive_container_traits specializations need to be added,
9091
* thus manually telling pybind11 about the recursion.
9192
*/
9293
struct MutuallyRecursiveContainerPairMV;
@@ -98,9 +99,13 @@ struct MutuallyRecursiveContainerPairVM : std::vector<MutuallyRecursiveContainer
9899
namespace pybind11 {
99100
namespace detail {
100101
template <typename SFINAE>
101-
struct is_recursive_container<MutuallyRecursiveContainerPairMV, SFINAE> : std::true_type {};
102+
struct recursive_container_traits<MutuallyRecursiveContainerPairMV, SFINAE> {
103+
using type_to_check_recursively = recursive_bottom;
104+
};
102105
template <typename SFINAE>
103-
struct is_recursive_container<MutuallyRecursiveContainerPairVM, SFINAE> : std::true_type {};
106+
struct recursive_container_traits<MutuallyRecursiveContainerPairVM, SFINAE> {
107+
using type_to_check_recursively = recursive_bottom;
108+
};
104109
} // namespace detail
105110
} // namespace pybind11
106111

0 commit comments

Comments
 (0)