@@ -43,6 +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 ===
77+ template <typename , typename , typename = void >
78+ struct is_move_insertable
79+ : std::false_type {};
80+
81+ template <typename T, typename A>
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 {};
86+
87+ template <typename T, typename A>
88+ inline constexpr bool is_move_insertable_v = is_move_insertable<T, A>::value;
89+
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;
46110}
47111
48112namespace stl
@@ -309,17 +373,61 @@ struct WrapSTLContainer<std::vector> : STLTypeWrapperBase<WrapSTLContainer<std::
309373 {
310374 using WrappedT = typename TypeWrapperT::type;
311375 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+
312405 wrapped.module ().set_override_module (stl_module ());
313406 wrapped.method (" cppsize" , &WrappedT::size);
314- wrapped.method (" resize" , [] (WrappedT& v, const cxxint_t s) { v.resize (s); });
407+
408+ // Although the C++ standard sates potential undefined behavior depending on
409+ // if T is DefaultInsertable/MoveInsertable/CopyInsertable, the only hard
410+ // type requirement for WrappedT<T>::resize is that if the current size
411+ // is less than the target resize, that T must be DefaultConstructable.
412+ if constexpr (std::is_default_constructible_v<T>)
413+ {
414+ wrapped.method (" resize" , [] (WrappedT& v, const cxxint_t s) { v.resize (s); });
415+ }
416+
417+ // The exposed `append' method uses WrappedT<T>::reserve, which requires
418+ // T to be MoveInsertible into WrappedT<T>. If this is not satisfied,
419+ // we can simply fall back to the slower non-reserved method.
315420 wrapped.method (" append" , [] (WrappedT& v, jlcxx::ArrayRef<T> arr)
316421 {
317422 const std::size_t addedlen = arr.size ();
318- v.reserve (v.size () + addedlen);
319- for (size_t i = 0 ; i != addedlen; ++i)
423+ if constexpr (jlcxx::detail::is_move_insertable_v<T, typename WrappedT::allocator_type>)
320424 {
321- v.push_back (arr[i] );
425+ v.reserve (v. size () + addedlen );
322426 }
427+ for (size_t i = 0 ; i != addedlen; ++i)
428+ {
429+ v.push_back (arr[i]);
430+ }
323431 });
324432 wrapped.module ().unset_override_module ();
325433 WrapVectorImpl<T>::wrap (wrapped);
@@ -364,10 +472,27 @@ struct WrapSTLContainer<std::deque> : STLTypeWrapperBase<WrapSTLContainer<std::d
364472 using T = typename WrappedT::value_type;
365473
366474 wrap_range_based_bsearch (wrapped);
367- 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+ }
368489 wrapped.module ().set_override_module (stl_module ());
369490 wrapped.method (" cppsize" , &WrappedT::size);
370- wrapped.method (" resize" , [](WrappedT &v, const cxxint_t s) { v.resize (s); });
491+ // Similar to the std::vector check, std::deque::resize requires DefaultConstrutable:
492+ if constexpr (std::is_default_constructible_v<T>)
493+ {
494+ wrapped.method (" resize" , [](WrappedT &v, const cxxint_t s) { v.resize (s); });
495+ }
371496 wrapped.method (" cxxgetindex" , [](const WrappedT& v, cxxint_t i) -> const_reftype<WrappedT> { return v[i - 1 ]; });
372497 wrapped.method (" cxxsetindex!" , [](WrappedT& v, const_reftype<WrappedT> val, cxxint_t i) { v[i - 1 ] = val; });
373498 wrapped.method (" push_back!" , [] (WrappedT& v, const_reftype<WrappedT> val) { v.push_back (val); });
0 commit comments