Skip to content

Commit 007ef98

Browse files
authored
Conditionally include resize methods / constructors depending on type traits (#191)
* Conditionally include resize methods depending on type traits * Exposed additional constructors
1 parent d892184 commit 007ef98

File tree

2 files changed

+147
-6
lines changed

2 files changed

+147
-6
lines changed

examples/types.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,13 @@ struct Foo
135135
std::vector<double> data;
136136
};
137137

138+
struct NeverEmpty
139+
{
140+
int data;
141+
NeverEmpty() = delete;
142+
NeverEmpty(int d) : data(d) {};
143+
};
144+
138145
struct NullableStruct { NullableStruct() {} };
139146

140147
struct IntDerived
@@ -189,6 +196,7 @@ void const_int_vec_arg(std::vector<std::shared_ptr<const int>>){}
189196
namespace jlcxx
190197
{
191198
template<> struct IsMirroredType<cpp_types::DoubleData> : std::false_type { };
199+
template<> struct IsMirroredType<cpp_types::NeverEmpty> : std::false_type { };
192200
template<typename T> struct IsSmartPointerType<cpp_types::MySmartPointer<T>> : std::true_type { };
193201
template<typename T> struct ConstructorPointerType<cpp_types::MySmartPointer<T>> { typedef std::shared_ptr<T> type; };
194202
}
@@ -465,6 +473,14 @@ JLCXX_MODULE define_julia_module(jlcxx::Module& types)
465473

466474
types.method("world_dequeue", []() { static World w; return std::deque({w}); });
467475
types.method("world_list", []() { static World w; return std::list({w}); });
476+
477+
types.add_type<NeverEmpty>("NeverEmpty")
478+
.constructor<int>()
479+
.method("get_data", [](NeverEmpty& n) { return n.data; })
480+
.method("set_data", [](NeverEmpty& n, int d) { n.data = d; });
481+
482+
types.method("neverempty_array", [] () { NeverEmpty n(1); return std::vector({n}); });
483+
types.method("neverempty_deque", [] () { NeverEmpty n(1); return std::deque({n}); });
468484
}
469485

470486
JLCXX_MODULE define_types2_module(jlcxx::Module& types2)

include/jlcxx/stl.hpp

Lines changed: 131 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,70 @@ struct SkipIfSameAs<T,T>
4343

4444
template<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

48112
namespace 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

Comments
 (0)