Skip to content

Commit 10bcc67

Browse files
committed
Exposed additional constructors
1 parent a18f6fe commit 10bcc67

File tree

2 files changed

+113
-3
lines changed

2 files changed

+113
-3
lines changed

examples/types.cpp

Lines changed: 15 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
@@ -465,6 +472,14 @@ JLCXX_MODULE define_julia_module(jlcxx::Module& types)
465472

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

470485
JLCXX_MODULE define_types2_module(jlcxx::Module& types2)

include/jlcxx/stl.hpp

Lines changed: 98 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,17 +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 ===
4677
template <typename, typename, typename = void>
4778
struct is_move_insertable
4879
: std::false_type {};
4980

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

5487
template <typename T, typename A>
5588
inline 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

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

Comments
 (0)