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