constexpr std::optional 可能的实现
constexpr std::optional possible implementation
我正在尝试在 constexpr
支持下实施 std::optional
作为一种实践。用法类似于:
constexpr optional<int> x(123);
int arr[*x];
在尝试实现这个时,我遇到了一个我无法解决的问题:在 optional<T>
对象中,我使用 std::aligned_storage_t<sizeof (T), alignof (T)>
对象来存储值,并使用在 optional<T>
的构造函数中放置 new 以将值构造到存储中。但是 placement new 不能在 constexpr
构造函数中使用:
constexpr optional(const T& value)
noexcept(std::is_nothrow_copy_constructible<T>::value)
: ...
{
new (ptr_to_storage) T(value); // this breaks `constexpr`
}
我还能如何实现它?
你可以使用联合。
看看 Andrzej 是如何做到的:
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(); }
};
注:
如果您想得到一个既支持在 constexpr
函数的局部变量中使用,又支持在运行时使用非平凡可破坏的值的答案,则此解决方案会有些复杂。 (可能,你确实想要支持这个,你不希望你的 constexpr optional
泄漏或者它不是常规可选的替代品。)
这是因为constexpr
析构函数必须根据语言规则默认,但在某些情况下必须协调调用泛型参数的析构函数。
在 Andrzej 的例子中,这是通过使用 SFINAE 并打开 std::is_trivially_destructible
来切换到 optional_base
class 的两种不同实现来解决的,一种带有默认析构函数,另一种没有。我在上面的列表中省略了它。如果你想要所有血淋淋的细节,我建议你阅读 Andrzej 的代码。
我正在尝试在 constexpr
支持下实施 std::optional
作为一种实践。用法类似于:
constexpr optional<int> x(123);
int arr[*x];
在尝试实现这个时,我遇到了一个我无法解决的问题:在 optional<T>
对象中,我使用 std::aligned_storage_t<sizeof (T), alignof (T)>
对象来存储值,并使用在 optional<T>
的构造函数中放置 new 以将值构造到存储中。但是 placement new 不能在 constexpr
构造函数中使用:
constexpr optional(const T& value)
noexcept(std::is_nothrow_copy_constructible<T>::value)
: ...
{
new (ptr_to_storage) T(value); // this breaks `constexpr`
}
我还能如何实现它?
你可以使用联合。
看看 Andrzej 是如何做到的:
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(); }
};
注:
如果您想得到一个既支持在 constexpr
函数的局部变量中使用,又支持在运行时使用非平凡可破坏的值的答案,则此解决方案会有些复杂。 (可能,你确实想要支持这个,你不希望你的 constexpr optional
泄漏或者它不是常规可选的替代品。)
这是因为constexpr
析构函数必须根据语言规则默认,但在某些情况下必须协调调用泛型参数的析构函数。
在 Andrzej 的例子中,这是通过使用 SFINAE 并打开 std::is_trivially_destructible
来切换到 optional_base
class 的两种不同实现来解决的,一种带有默认析构函数,另一种没有。我在上面的列表中省略了它。如果你想要所有血淋淋的细节,我建议你阅读 Andrzej 的代码。