5

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?

Siler
  • 8,976
  • 11
  • 64
  • 124
  • Why do you have to check? Can't you just copy `other`'s union to `this`'s? – NathanOliver Oct 31 '19 at 20:35
  • But what if the union has a non-trivial copy constructor? – Siler Oct 31 '19 at 20:36
  • Oh yeah. If you have a `std::string` as the member then there would be no copy constructor. – NathanOliver Oct 31 '19 at 20:38
  • Note that `emplace` is not `constexpr`. I wouldn't expect the standard to require the copy constructor use only constexpr-friendly mechanisms to initialize the object while `emplace` doesn't have to when it's the same operation on the "actually need to do something" path. Therefore, I suspect `constexpr` would apply only to the case where the copied-from object is disengaged. – chris Oct 31 '19 at 20:56
  • @chris You cannot change the active member of a union (in C++17) or placement-new a new object in a constant expressions, but you can initialize and copy a union with the implicitly defined copy constructor. So copy construction is possible in either case (if T is trivially copyable), but `emplace` is not. – walnut Oct 31 '19 at 21:44
  • @uneven_mark, Right, I was focusing on the non-trivially-copyable Ts per the above comments. – chris Oct 31 '19 at 21:56
  • @chris Ah ok, but in that case it turns out that the standard specification gives no requirement on `constexpr`ness of the copy constructor at all: https://timsong-cpp.github.io/cppwp/n4659/optional#ctor-6 – walnut Oct 31 '19 at 22:01

1 Answers1

6

For C++17, see [optional.ctor]/6:

... If is_trivially_copy_constructible_v<T> is true, this constructor shall be a constexpr 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.

Brian Bi
  • 111,498
  • 10
  • 176
  • 312
  • From which version of the standard is this citation from? Does not match the draft on [http://eel.is/c++draft/optional.ctor](http://eel.is/c++draft/optional.ctor). – Holt Oct 31 '19 at 20:43
  • @Holt The current one (C++17). – Brian Bi Oct 31 '19 at 20:43
  • @Holt It surprises me that the current draft has different wording. I'll look into it. – Brian Bi Oct 31 '19 at 20:44
  • 1
    @Holt C++20 allows `new` in constexpr functions so the wording most likely has changed. – NathanOliver Oct 31 '19 at 20:44
  • @NathanOliver-ReinstateMonica But only the global replaceable (allocating) allocation function may be used, so placement-new into allocated storage is still not possible. https://eel.is/c++draft/expr#const-4.17 How do you implement it with only that? – walnut Oct 31 '19 at 20:48
  • @uneven_mark Good call. Not sure what changed to allow the change then. – NathanOliver Oct 31 '19 at 20:55