17

In C++17, the new std::optional mandates that it be trivially destructible if T is trivially destructible in [optional.object.dtor]:

~optional();
1 Effects: If is_trivially_destructible_v<T> != true and *this contains a value, calls val->T::~T().
2 Remarks: If is_trivially_destructible_v<T> == true then this destructor shall be a trivial destructor.

So this potential implementation fragment would be non-conforming to the standard:

template <class T>
struct wrong_optional {
    union { T value; };
    bool on;

    ~wrong_optional() { if (on) { value.~T(); } }
};

My question is: what is the advantage of this mandate? Presumably, for trivially destructible types, the compiler can figure out that value.~T() is a no-op and emit no code for wrong_optional<T>::~wrong_optional().

Barry
  • 286,269
  • 29
  • 621
  • 977
  • Other types may rely on the fact that given type is trivially destructible or not. (as destruction of `std::vector` ?). – Jarod42 Jan 27 '17 at 15:35

3 Answers3

17

std::optional already has constexpr constructors. When its destructor is trivial, it is a literal type. Only objects of literal types can be created and manipulated in constant expressions.

shargors
  • 2,147
  • 1
  • 15
  • 21
cpplearner
  • 13,776
  • 2
  • 47
  • 72
14

A type being trivially destructible is its own reward. Here are just a few of the advantages of having a trivial destructor:

  1. The type can be trivially copyable. And this make the type eligible for all kinds of optimizations. Visual Studio's standard library implementation has a number of optimizations for dealing with such types.

  2. It's legal to not bother calling the destructor of trivially destructible types. You can just deallocate their storage. This is kind of a low-level thing to do, but it can have advantages. It's part of what allows implementations to do those optimizations above.

  3. Types with trivial destructors can be literal types, and are thus objects which can be constructed and manipulated at compile time.

optional<T>'s interface is attempting not to interfere with the behavior of T as much as possible. So if you could do something with T, then you ought to be able to do that same thing with optional<T>. Unless there is a really good reason not to.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • Regarding (1), `std::optional` isn't mandated to be trivially copyable though. Do you think it should be? – Barry Jan 27 '17 at 17:34
  • @Barry: The better question is "can it be"? And the answer is no, it cannot. `optional` effectively contains a "boolean" and some storage of `sizeof(T)` bytes that may or may not be initialized to contain an object. If the incoming storage contains an object, merely bit-copying it will not cause the destination storage to contain an object. `optional` can still be trivially destructible if `T` is trivially destructible, but it cannot be trivially copyable. – Nicol Bolas Jan 27 '17 at 17:38
  • 2
    If `T` is trivially copyable, you could implement `optional` to be trivially copyable. – Barry Jan 27 '17 at 17:54
  • @Barry: What part of "the answer is no" did you not understand ;) – Nicol Bolas Jan 27 '17 at 17:58
  • The part where I think you're wrong? `union { char; T; } bool; ` could be trivially copyable. – Barry Jan 27 '17 at 17:59
  • @Barry: I explained exactly why you can't. You didn't explain why that reasoning was incorrect. If you wish to learn more, read [basic.types]/2 and 3. – Nicol Bolas Jan 27 '17 at 18:00
  • That section isn't relevant. For `optional` to be `constexpr`, it basically has to be implemented the way I specified. That type is trivially copyable iff `T` is. – Barry Jan 27 '17 at 18:02
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/134211/discussion-between-nicol-bolas-and-barry). – Nicol Bolas Jan 27 '17 at 18:04
3

Per this Q/A, one specific reason not yet mentioned and worth mentioning is: a type being trivially copyable and trivially destructible allows it to be passed in registers in the ABI (see Agner Fog's calling conventions). This can have significant effects on the generated code. A simple function like:

std::optional<int> get() { return {42}; }

Might emit the following code if the type isn't trivially copyable/destructible:

    mov     rax, rdi
    mov     DWORD PTR [rdi], 42
    mov     BYTE PTR [rdi+4], 1
    ret

But could emit just the following if it were:

    movabs  rax, 4294967338
    ret

That's definitely better.

Barry
  • 286,269
  • 29
  • 621
  • 977