@@ -822,56 +822,173 @@ using movable_cast_op_type
822
822
typename std::add_rvalue_reference<intrinsic_t <T>>::type,
823
823
typename std::add_lvalue_reference<intrinsic_t <T>>::type>>;
824
824
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
+
834
846
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
+
847
855
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.
856
866
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
+ };
859
871
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
+ };
862
880
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.
866
881
template <typename Container>
867
- struct is_move_constructible <
882
+ struct container_value_type_traits <
868
883
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 {};
875
992
876
993
// Likewise for std::pair
877
994
// (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>>
883
1000
884
1001
// std::is_copy_constructible isn't quite enough: it lets std::vector<T> (and similar) through when
885
1002
// 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>> {};
888
1008
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 {};
901
1011
902
1012
// Likewise for std::pair
903
1013
// (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>>
908
1018
: all_of<is_copy_constructible<T1>, is_copy_constructible<T2>> {};
909
1019
910
1020
// 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
+
922
1031
template <typename T1, typename T2>
923
1032
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>> {};
929
1034
930
1035
PYBIND11_NAMESPACE_END (detail)
931
1036
0 commit comments