@@ -822,56 +822,157 @@ 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+ 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+
825838// True if Container has a dependent type mapped_type that is equivalent
826839// to Container itself
827840// 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 {};
841+ template <typename Container, typename SFINAE = void >
842+ struct container_mapped_type_traits {
843+ static constexpr bool has_mapped_type = false ;
844+ static constexpr bool has_recursive_mapped_type = false ;
845+ };
830846
831847// This specialization is only valid if both conditions are fulfilled:
832848// 1) The mapped_type exists
833849// 2) And it is equivalent to Container
834850template <typename Container>
835- struct is_container_with_recursive_mapped_type <Container, typename Container::mapped_type>
836- : std::true_type {};
851+ struct container_mapped_type_traits <
852+ Container,
853+ typename std::enable_if<
854+ std::is_same<typename Container::mapped_type, Container>::value>::type> {
855+ static constexpr bool has_mapped_type = true ;
856+ static constexpr bool has_recursive_mapped_type = true ;
857+ };
858+
859+ template <typename Container>
860+ struct container_mapped_type_traits <
861+ Container,
862+ typename std::enable_if<
863+ negation<std::is_same<typename Container::mapped_type, Container>>::value>::type> {
864+ static constexpr bool has_mapped_type = true ;
865+ static constexpr bool has_recursive_mapped_type = false ;
866+ };
837867
838868// True if Container has a dependent type value_type that is equivalent
839869// to Container itself
840870// 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 {};
871+ template <typename Container, typename SFINAE = void >
872+ struct container_value_type_traits : std::false_type {
873+ static constexpr bool has_value_type = false ;
874+ static constexpr bool has_recursive_value_type = false ;
875+ };
843876
844877// This specialization is only valid if both conditions are fulfilled:
845878// 1) The value_type exists
846879// 2) And it is equivalent to Container
847880template <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- 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>> {};
881+ struct container_value_type_traits <
882+ Container,
883+ typename std::enable_if<
884+ std::is_same<typename Container::value_type, Container>::value>::type> {
885+ static constexpr bool has_value_type = true ;
886+ static constexpr bool has_recursive_value_type = true ;
887+ };
888+
889+ template <typename Container>
890+ struct container_value_type_traits <
891+ Container,
892+ typename std::enable_if<
893+ negation<std::is_same<typename Container::value_type, Container>>::value>::type> {
894+ static constexpr bool has_value_type = true ;
895+ static constexpr bool has_recursive_value_type = false ;
896+ };
859897
860- template <typename T, typename SFINAE = void >
861- struct is_move_constructible : std::is_move_constructible<T> {};
898+ /*
899+ * Tag to be used for representing the bottom of recursively defined types.
900+ * Define this tag so we don't have to use void.
901+ */
902+ struct recursive_bottom {};
903+
904+ /*
905+ * Implementation detail of `recursive_container_traits` below.
906+ * `T` is the `value_type` of the container, which might need to be modified to
907+ * avoid recursive types and const types.
908+ */
909+ template <typename T>
910+ struct impl_type_to_check_recursively {
911+ /*
912+ * If the container is recursive, then no further recursion should be done.
913+ */
914+ using if_recursive = recursive_bottom;
915+ /*
916+ * Otherwise yield `T` unchanged.
917+ */
918+ using if_not_recursive = T;
919+ };
920+
921+ /*
922+ * For pairs, the first type should remove the `const`.
923+ * Also, if the container is recursive, then the recursive checking should consider
924+ * the first type only.
925+ */
926+ template <typename A, typename B>
927+ struct impl_type_to_check_recursively <std::pair<A, B>> {
928+ using if_recursive = typename std::remove_const<A>::type;
929+ using if_not_recursive = std::pair<typename std::remove_const<A>::type, B>;
930+ };
931+
932+ template <typename Container, typename SFINAE = void >
933+ struct impl_recursive_container_traits {
934+ using type_to_check_recursively = recursive_bottom;
935+ };
862936
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.
866937template <typename Container>
867- struct is_move_constructible <
938+ struct impl_recursive_container_traits <
868939 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> {};
940+ typename std::enable_if<container_value_type_traits<Container>::has_value_type>::type> {
941+ static constexpr bool is_recursive
942+ = container_mapped_type_traits<Container>::has_recursive_mapped_type
943+ || container_value_type_traits<Container>::has_recursive_value_type;
944+ /*
945+ * This member dictates which type Pybind11 should check recursively in traits
946+ * such as `is_move_constructible`, `is_copy_constructible`, `is_move_assignable`, ...
947+ * Direct access to `value_type` should be avoided:
948+ * 1. `value_type` might recursively contain the type again
949+ * 2. `value_type` of STL map types is `std::pair<A const, B>`, the `const`
950+ * should be removed.
951+ *
952+ */
953+ using type_to_check_recursively = typename if_then_else<
954+ is_recursive,
955+ typename impl_type_to_check_recursively<typename Container::value_type>::if_recursive,
956+ typename impl_type_to_check_recursively<
957+ typename Container::value_type>::if_not_recursive>::type;
958+ };
959+
960+ /*
961+ * This is exactly the same as impl_recursive_container_traits.
962+ * This duplication achieves that user-defined specializations don't compete
963+ * with internal specializations, but take precedence.
964+ */
965+ template <typename Container, typename SFINAE = void >
966+ struct recursive_container_traits : impl_recursive_container_traits<Container> {};
967+
968+ template <typename T>
969+ struct is_move_constructible
970+ : all_of<std::is_move_constructible<T>,
971+ is_move_constructible<
972+ typename recursive_container_traits<T>::type_to_check_recursively>> {};
973+
974+ template <>
975+ struct is_move_constructible <recursive_bottom> : std::true_type {};
875976
876977// Likewise for std::pair
877978// (after C++17 it is mandatory that the move constructor not exist when the two types aren't
@@ -883,21 +984,14 @@ struct is_move_constructible<std::pair<T1, T2>>
883984
884985// std::is_copy_constructible isn't quite enough: it lets std::vector<T> (and similar) through when
885986// 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> {};
987+ template <typename T>
988+ struct is_copy_constructible
989+ : all_of<std::is_copy_constructible<T>,
990+ is_copy_constructible<
991+ typename recursive_container_traits<T>::type_to_check_recursively>> {};
888992
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> {};
993+ template <>
994+ struct is_copy_constructible <recursive_bottom> : std::true_type {};
901995
902996// Likewise for std::pair
903997// (after C++17 it is mandatory that the copy constructor not exist when the two types aren't
@@ -908,24 +1002,19 @@ struct is_copy_constructible<std::pair<T1, T2>>
9081002 : all_of<is_copy_constructible<T1>, is_copy_constructible<T2>> {};
9091003
9101004// 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> {};
1005+ template <typename T>
1006+ struct is_copy_assignable
1007+ : all_of<
1008+ std::is_copy_assignable<T>,
1009+ is_copy_assignable<typename recursive_container_traits<T>::type_to_check_recursively>> {
1010+ };
1011+
1012+ template <>
1013+ struct is_copy_assignable <recursive_bottom> : std::true_type {};
1014+
9221015template <typename T1, typename T2>
9231016struct 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>> {};
1017+ : all_of<is_copy_assignable<T1>, is_copy_assignable<T2>> {};
9291018
9301019PYBIND11_NAMESPACE_END (detail)
9311020
0 commit comments