@@ -826,26 +826,179 @@ using movable_cast_op_type
826
826
typename std::add_rvalue_reference<intrinsic_t <T>>::type,
827
827
typename std::add_lvalue_reference<intrinsic_t <T>>::type>>;
828
828
829
- // std::is_copy_constructible isn't quite enough: it lets std::vector<T> (and similar) through when
830
- // T is non-copyable, but code containing such a copy constructor fails to actually compile.
831
- template <typename T, typename SFINAE = void >
832
- struct is_copy_constructible : std::is_copy_constructible<T> {};
829
+ // Does the container have a mapped type and is it recursive?
830
+ // Implemented by specializations below.
831
+ template <typename Container, typename SFINAE = void >
832
+ struct container_mapped_type_traits {
833
+ static constexpr bool has_mapped_type = false ;
834
+ static constexpr bool has_recursive_mapped_type = false ;
835
+ };
836
+
837
+ template <typename Container>
838
+ struct container_mapped_type_traits <
839
+ Container,
840
+ typename std::enable_if<
841
+ std::is_same<typename Container::mapped_type, Container>::value>::type> {
842
+ static constexpr bool has_mapped_type = true ;
843
+ static constexpr bool has_recursive_mapped_type = true ;
844
+ };
845
+
846
+ template <typename Container>
847
+ struct container_mapped_type_traits <
848
+ Container,
849
+ typename std::enable_if<
850
+ negation<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 = false ;
853
+ };
854
+
855
+ // Does the container have a value type and is it recursive?
856
+ // Implemented by specializations below.
857
+ template <typename Container, typename SFINAE = void >
858
+ struct container_value_type_traits : std::false_type {
859
+ static constexpr bool has_value_type = false ;
860
+ static constexpr bool has_recursive_value_type = false ;
861
+ };
862
+
863
+ template <typename Container>
864
+ struct container_value_type_traits <
865
+ Container,
866
+ typename std::enable_if<
867
+ std::is_same<typename Container::value_type, Container>::value>::type> {
868
+ static constexpr bool has_value_type = true ;
869
+ static constexpr bool has_recursive_value_type = true ;
870
+ };
871
+
872
+ template <typename Container>
873
+ struct container_value_type_traits <
874
+ Container,
875
+ typename std::enable_if<
876
+ negation<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 = false ;
879
+ };
880
+
881
+ /*
882
+ * Tag to be used for representing the bottom of recursively defined types.
883
+ * Define this tag so we don't have to use void.
884
+ */
885
+ struct recursive_bottom {};
886
+
887
+ /*
888
+ * Implementation detail of `recursive_container_traits` below.
889
+ * `T` is the `value_type` of the container, which might need to be modified to
890
+ * avoid recursive types and const types.
891
+ */
892
+ template <typename T, bool is_this_a_map>
893
+ struct impl_type_to_check_recursively {
894
+ /*
895
+ * If the container is recursive, then no further recursion should be done.
896
+ */
897
+ using if_recursive = recursive_bottom;
898
+ /*
899
+ * Otherwise yield `T` unchanged.
900
+ */
901
+ using if_not_recursive = T;
902
+ };
903
+
904
+ /*
905
+ * For pairs - only as value type of a map -, the first type should remove the `const`.
906
+ * Also, if the map is recursive, then the recursive checking should consider
907
+ * the first type only.
908
+ */
909
+ template <typename A, typename B>
910
+ struct impl_type_to_check_recursively <std::pair<A, B>, /* is_this_a_map = */ true > {
911
+ using if_recursive = typename std::remove_const<A>::type;
912
+ using if_not_recursive = std::pair<typename std::remove_const<A>::type, B>;
913
+ };
833
914
834
- template <typename T, typename SFINAE = void >
835
- struct is_move_constructible : std::is_move_constructible<T> {};
915
+ /*
916
+ * Implementation of `recursive_container_traits` below.
917
+ */
918
+ template <typename Container, typename SFINAE = void >
919
+ struct impl_recursive_container_traits {
920
+ using type_to_check_recursively = recursive_bottom;
921
+ };
836
922
837
- // Specialization for types that appear to be copy constructible but also look like stl containers
838
- // (we specifically check for: has `value_type` and `reference` with `reference = value_type&`): if
839
- // so, copy constructability depends on whether the value_type is copy constructible.
840
923
template <typename Container>
841
- struct is_copy_constructible <
924
+ struct impl_recursive_container_traits <
842
925
Container,
843
- enable_if_t <
844
- all_of<std::is_copy_constructible<Container>,
845
- std::is_same<typename Container::value_type &, typename Container::reference>,
846
- // Avoid infinite recursion
847
- negation<std::is_same<Container, typename Container::value_type>>>::value>>
848
- : is_copy_constructible<typename Container::value_type> {};
926
+ typename std::enable_if<container_value_type_traits<Container>::has_value_type>::type> {
927
+ static constexpr bool is_recursive
928
+ = container_mapped_type_traits<Container>::has_recursive_mapped_type
929
+ || container_value_type_traits<Container>::has_recursive_value_type;
930
+ /*
931
+ * This member dictates which type Pybind11 should check recursively in traits
932
+ * such as `is_move_constructible`, `is_copy_constructible`, `is_move_assignable`, ...
933
+ * Direct access to `value_type` should be avoided:
934
+ * 1. `value_type` might recursively contain the type again
935
+ * 2. `value_type` of STL map types is `std::pair<A const, B>`, the `const`
936
+ * should be removed.
937
+ *
938
+ */
939
+ using type_to_check_recursively = typename std::conditional<
940
+ is_recursive,
941
+ typename impl_type_to_check_recursively<
942
+ typename Container::value_type,
943
+ container_mapped_type_traits<Container>::has_mapped_type>::if_recursive,
944
+ typename impl_type_to_check_recursively<
945
+ typename Container::value_type,
946
+ container_mapped_type_traits<Container>::has_mapped_type>::if_not_recursive>::type;
947
+ };
948
+
949
+ /*
950
+ * This trait defines the `type_to_check_recursively` which is needed to properly
951
+ * handle recursively defined traits such as `is_move_constructible` without going
952
+ * into an infinite recursion.
953
+ * Should be used instead of directly accessing the `value_type`.
954
+ * It cancels the recursion by returning the `recursive_bottom` tag.
955
+ *
956
+ * The default definition of `type_to_check_recursively` is as follows:
957
+ *
958
+ * 1. By default, it is `recursive_bottom`, so that the recursion is canceled.
959
+ * 2. If the type is non-recursive and defines a `value_type`, then the `value_type` is used.
960
+ * If the `value_type` is a pair and a `mapped_type` is defined,
961
+ * then the `const` is removed from the first type.
962
+ * 3. If the type is recursive and `value_type` is not a pair, then `recursive_bottom` is returned.
963
+ * 4. If the type is recursive and `value_type` is a pair and a `mapped_type` is defined,
964
+ * then `const` is removed from the first type and the first type is returned.
965
+ *
966
+ * This behavior can be extended by the user as seen in test_stl_binders.cpp.
967
+ *
968
+ * This struct is exactly the same as impl_recursive_container_traits.
969
+ * The duplication achieves that user-defined specializations don't compete
970
+ * with internal specializations, but take precedence.
971
+ */
972
+ template <typename Container, typename SFINAE = void >
973
+ struct recursive_container_traits : impl_recursive_container_traits<Container> {};
974
+
975
+ template <typename T>
976
+ struct is_move_constructible
977
+ : all_of<std::is_move_constructible<T>,
978
+ is_move_constructible<
979
+ typename recursive_container_traits<T>::type_to_check_recursively>> {};
980
+
981
+ template <>
982
+ struct is_move_constructible <recursive_bottom> : std::true_type {};
983
+
984
+ // Likewise for std::pair
985
+ // (after C++17 it is mandatory that the move constructor not exist when the two types aren't
986
+ // themselves move constructible, but this can not be relied upon when T1 or T2 are themselves
987
+ // containers).
988
+ template <typename T1, typename T2>
989
+ struct is_move_constructible <std::pair<T1, T2>>
990
+ : all_of<is_move_constructible<T1>, is_move_constructible<T2>> {};
991
+
992
+ // std::is_copy_constructible isn't quite enough: it lets std::vector<T> (and similar) through when
993
+ // T is non-copyable, but code containing such a copy constructor fails to actually compile.
994
+ template <typename T>
995
+ struct is_copy_constructible
996
+ : all_of<std::is_copy_constructible<T>,
997
+ is_copy_constructible<
998
+ typename recursive_container_traits<T>::type_to_check_recursively>> {};
999
+
1000
+ template <>
1001
+ struct is_copy_constructible <recursive_bottom> : std::true_type {};
849
1002
850
1003
// Likewise for std::pair
851
1004
// (after C++17 it is mandatory that the copy constructor not exist when the two types aren't
@@ -856,14 +1009,16 @@ struct is_copy_constructible<std::pair<T1, T2>>
856
1009
: all_of<is_copy_constructible<T1>, is_copy_constructible<T2>> {};
857
1010
858
1011
// The same problems arise with std::is_copy_assignable, so we use the same workaround.
859
- template <typename T, typename SFINAE = void >
860
- struct is_copy_assignable : std::is_copy_assignable<T> {};
861
- template <typename Container>
862
- struct is_copy_assignable <Container,
863
- enable_if_t <all_of<std::is_copy_assignable<Container>,
864
- std::is_same<typename Container::value_type &,
865
- typename Container::reference>>::value>>
866
- : is_copy_assignable<typename Container::value_type> {};
1012
+ template <typename T>
1013
+ struct is_copy_assignable
1014
+ : all_of<
1015
+ std::is_copy_assignable<T>,
1016
+ is_copy_assignable<typename recursive_container_traits<T>::type_to_check_recursively>> {
1017
+ };
1018
+
1019
+ template <>
1020
+ struct is_copy_assignable <recursive_bottom> : std::true_type {};
1021
+
867
1022
template <typename T1, typename T2>
868
1023
struct is_copy_assignable <std::pair<T1, T2>>
869
1024
: all_of<is_copy_assignable<T1>, is_copy_assignable<T2>> {};
0 commit comments