Reputation: 9504
I'm very confused about how std::optional
's copy constructor is supposed to be implemented to meet the requirements of being constexpr
.
Note there are many other questions on Stackoverflow asking something similar, such as:
How to implement std::optional's copy constructor?
std::optional implemented as union vs char[]/aligned_storage
However, neither of these questions are actually asking about the COPY CONSTRUCTOR. I am specifically asking about the copy constructor, with a function signature (from https://en.cppreference.com/w/cpp/utility/optional/optional) as follows:
constexpr optional( const optional& other );
Now, I've read enough about std::optional
to know the basics. A typical mistake implementors make is to try and implement it with std::aligned_storage
. Since placement new cannot work in a constexpr
(at least in C++17), this won't work. Instead, a union type needs to be used so it can be directly constructed. Something like:
struct dummy_type {};
union optional_impl
{
dummy_type m_dummy;
T m_value;
};
Okay, but still... I still don't see how we are supposed to meet the requirements of implementing the copy constructor as constexpr
. The problem is that in the copy constructor, we need to check if other.has_value()
is true. If it is, we want to directly copy *other
, otherwise we just want to initialize m_dummy
. But how can we express this conditional decision in a constexpr
copy constructor?
constexpr optional( const optional& other ) : m_dummy{}
{
if (other.has_value()) new (&m_value) T(*other); // Wrong! Can't use placement new
}
The only way I can see this working is using placement new.
So I checked some actual implemenations, like the gcc implementation here:
https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/std/optional#L248
And indeed... they just use placement new. And in fact, the copy constructor isn't event constexpr
(which I guess is a defect).
Here's another implementation:
https://github.com/akrzemi1/Optional/blob/master/optional.hpp#L416
And again, they just use placement new.
So how can optional(const optional&)
ever be implemented as a constexpr
? Is this a defect in the standard or something?
Upvotes: 6
Views: 3486
Reputation: 119467
For C++17, see [optional.ctor]/6:
... If
is_trivially_copy_constructible_v<T>
istrue
, this constructor shall be aconstexpr
constructor.
In that case, the union will also be trivially copyable, so there is no problem.
In all other cases, the constructor will still carry the constexpr
specifier even though it can't be used in a constant expression. This is ok: a function template (or member function of class template) is allowed to be declared constexpr
as long as it has at least one possible instantiation that is usable in constant expressions. ([dcl.constexpr]/6)
In C++20, the wording has changed thanks to P0602R4. However, I think that this did not change the constexpr
requirements. If T
is trivially copyable, then the constructor is trivial, which implies that it's also constexpr
. If T
is not trivially copyable, then the standard does not say the constructor must be usable in constant expressions, so there is no such requirement.
Upvotes: 6