Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

constexpr std::optional possible implementation

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?

like image 413
Zizheng Tai Avatar asked Jun 18 '16 07:06

Zizheng Tai


1 Answers

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.

like image 101
Chris Beck Avatar answered Sep 30 '22 10:09

Chris Beck