@@ -43,17 +43,70 @@ struct SkipIfSameAs<T,T>
4343
4444template <typename T1, typename T2> using skip_if_same = typename SkipIfSameAs<T1,T2>::type;
4545
46+
47+ // === is_default_insertable ===
48+ // https://stackoverflow.com/a/78556159
49+ template <typename , typename , typename = void >
50+ struct is_default_insertable
51+ : std::false_type {};
52+
53+ template <typename T, typename A>
54+ struct is_default_insertable <
55+ T, A,
56+ std::void_t <decltype (std::allocator_traits<A>::construct(std::declval<A&>(), std::declval<T*>()))>
57+ > : std::true_type {};
58+
59+ template <typename T, typename A>
60+ inline constexpr bool is_default_insertable_v = is_default_insertable<T, A>::value;
61+
62+ // === is_copy_insertable ===
63+ template <typename , typename , typename = void >
64+ struct is_copy_insertable : std::false_type {};
65+
66+ template <typename T, typename A>
67+ struct is_copy_insertable <
68+ T, A,
69+ std::void_t <
70+ decltype (std::allocator_traits<A>::construct(std::declval<A&>(), std::declval<T*>(), std::declval<const T&>()))>
71+ > : std::true_type {};
72+
73+ template <typename T, typename A>
74+ inline constexpr bool is_copy_insertable_v = is_copy_insertable<T, A>::value;
75+
76+ // === is_move_insertable ===
4677template <typename , typename , typename = void >
4778struct is_move_insertable
4879 : std::false_type {};
4980
5081template <typename T, typename A>
51- struct is_move_insertable <T, A, std::void_t <decltype (std::allocator_traits<A>::construct(std::declval<A&>(), std::declval<T*>(), std::declval<T&&>()))>>
52- : std::true_type {};
82+ struct is_move_insertable <
83+ T, A,
84+ std::void_t <decltype (std::allocator_traits<A>::construct(std::declval<A&>(), std::declval<T*>(), std::declval<T&&>()))>
85+ > : std::true_type {};
5386
5487template <typename T, typename A>
5588inline constexpr bool is_move_insertable_v = is_move_insertable<T, A>::value;
5689
90+ // === is_std_allocator ===
91+ template <typename >
92+ struct is_std_allocator : std::false_type {};
93+
94+ template <typename T>
95+ struct is_std_allocator <std::allocator<T>> : std::true_type {};
96+
97+ template <typename T>
98+ inline constexpr bool is_std_allocator_v = is_std_allocator<T>::value;
99+
100+ // === uses_std_allocator ===
101+ template <typename C, typename = void >
102+ struct uses_std_allocator : std::false_type {};
103+
104+ template <typename C>
105+ struct uses_std_allocator <C, std::void_t <typename C::allocator_type>>
106+ : is_std_allocator<typename C::allocator_type> {};
107+
108+ template <typename C>
109+ inline constexpr bool uses_std_allocator_v = uses_std_allocator<C>::value;
57110}
58111
59112namespace stl
@@ -320,6 +373,35 @@ struct WrapSTLContainer<std::vector> : STLTypeWrapperBase<WrapSTLContainer<std::
320373 {
321374 using WrappedT = typename TypeWrapperT::type;
322375 using T = typename WrappedT::value_type;
376+
377+ // The C++ standard notes that std::vector<T>(size_t count) requires T to be
378+ // DefaultInsertible, else behavior is undefined. Undefined behavior may
379+ // include a compile time error (that std::is_constructable would not catch).
380+ // - Note: This is tested to be the case for std::deque in GCC 15.2.1
381+ // This cannot be checked in an if constexpr context prior to C++20, so we
382+ // simply remove the size_t constructor if the type is not default insertable.
383+ // We also check std::is_default_constructible since std's allocator will
384+ // internally call the default constructor (which is_default_insertable
385+ // will not check). Custom allocators may not literally call the default
386+ // constructor, so only check if the standard allocator is used.
387+ if constexpr (jlcxx::detail::is_default_insertable_v<T, typename WrappedT::allocator_type>
388+ && (!jlcxx::detail::uses_std_allocator_v<WrappedT>
389+ || std::is_default_constructible_v<T>))
390+ {
391+ wrapped.template constructor <std::size_t >();
392+ }
393+ // Similarly, we expose the count-copy constructor gated on CopyInsertible
394+ if constexpr (jlcxx::detail::is_copy_insertable_v<T, typename WrappedT::allocator_type>
395+ && (!jlcxx::detail::uses_std_allocator_v<WrappedT>
396+ || std::is_copy_constructible_v<T>))
397+ {
398+ // Since references to Julia owned objects may be of incompatible types
399+ // and a copy will be performed anyway over the bindings, pass by
400+ // value into this constructor instead of reference.
401+ using BaseType = std::remove_const_t <std::remove_reference_t <T>>;
402+ wrapped.template constructor <std::size_t , BaseType>();
403+ }
404+
323405 wrapped.module ().set_override_module (stl_module ());
324406 wrapped.method (" cppsize" , &WrappedT::size);
325407
@@ -390,7 +472,20 @@ struct WrapSTLContainer<std::deque> : STLTypeWrapperBase<WrapSTLContainer<std::d
390472 using T = typename WrappedT::value_type;
391473
392474 wrap_range_based_bsearch (wrapped);
393- wrapped.template constructor <std::size_t >();
475+ // Similar to the std::vector check, we gate the additional constructors:
476+ if constexpr (jlcxx::detail::is_default_insertable_v<T, typename WrappedT::allocator_type>
477+ && (!jlcxx::detail::uses_std_allocator_v<WrappedT>
478+ || std::is_default_constructible_v<T>))
479+ {
480+ wrapped.template constructor <std::size_t >();
481+ }
482+ if constexpr (jlcxx::detail::is_copy_insertable_v<T, typename WrappedT::allocator_type>
483+ && (!jlcxx::detail::uses_std_allocator_v<WrappedT>
484+ || std::is_copy_constructible_v<T>))
485+ {
486+ using BaseType = std::remove_const_t <std::remove_reference_t <T>>;
487+ wrapped.template constructor <std::size_t , BaseType>();
488+ }
394489 wrapped.module ().set_override_module (stl_module ());
395490 wrapped.method (" cppsize" , &WrappedT::size);
396491 // Similar to the std::vector check, std::deque::resize requires DefaultConstrutable:
0 commit comments