I'm trying to implement std::optional
with constexpr
support as a practice. The usage will be something like:
constexpr optional<int> x(123);
int arr[*x];
When trying to implement this I got this one problem that I haven't been able to solve: Inside an optional<T>
object, I use a std::aligned_storage_t<sizeof (T), alignof (T)>
object to store the value, and use placement new in optional<T>
's constructor to construct value into the storage. But placement new cannot be used inside a constexpr
constructor:
constexpr optional(const T& value)
noexcept(std::is_nothrow_copy_constructible<T>::value)
: ...
{
new (ptr_to_storage) T(value); // this breaks `constexpr`
}
How else can I implement this?
You could use a union.
Check out how Andrzej does it:
https://github.com/akrzemi1/Optional/blob/master/optional.hpp#L282
template <class T>
union storage_t
{
unsigned char dummy_;
T value_;
constexpr storage_t( trivial_init_t ) noexcept : dummy_() {};
template <class... Args>
constexpr storage_t( Args&&... args ) : value_(constexpr_forward<Args>(args)...) {}
~storage_t() = default;
};
template <class T>
struct optional_base
{
bool init_;
storage_t<T> storage_;
constexpr optional_base() noexcept : init_(false), storage_(trivial_init) {};
explicit constexpr optional_base(const T& v) : init_(true), storage_(v) {}
explicit constexpr optional_base(T&& v) : init_(true), storage_(constexpr_move(v)) {}
template <class... Args> explicit optional_base(in_place_t, Args&&... args)
: init_(true), storage_(constexpr_forward<Args>(args)...) {}
template <class U, class... Args, TR2_OPTIONAL_REQUIRES(is_constructible<T, std::initializer_list<U>>)>
explicit optional_base(in_place_t, std::initializer_list<U> il, Args&&... args)
: init_(true), storage_(il, std::forward<Args>(args)...) {}
~optional_base() { if (init_) storage_.value_.T::~T(); }
};
Note:
There is some complexity in this solution if you want to get an answer that supports both use in local variables in constexpr
functions, and use at runtime with values that are nontrivially destructible. (Probably, you do want to support this, you don't want your constexpr optional
to leak or it isn't a drop-in replacement for the regular optional.)
This is because the constexpr
destructor must be defaulted according to language rules, but that has to be reconciled with the need to call the destructor of the generic parameter in some cases.
In Andrzej's example, this is resolved by using SFINAE and switching on std::is_trivially_destructible
to switch to two different implementations of the optional_base
class, one with defaulted destructor and one without. I omitted that in the above listing. If you want all the gory details I suggest you read Andrzej's code.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With