Question

is optional<void> type name valid?

I discovered by accident while modifying existing code that an alias declaration created std::optional<void>. It came from template code like this:

using ret_t = std::invoke_result_t<Fn, Args...>;
using opt_ret_t = std::optional<ret_t>;

The code later discriminates the case when the return type is (not) void, so there is never actually an optional object created.

To make sure I wasn't missing something, I added an assert in that branch of code and everything compiles:

static_assert(std::is_same_v<opt_ret_t, std::optional<void>>);

cppreference says:

There are no optional references, functions, arrays, or cv void; a program is ill-formed if it instantiates an optional with such a type.

So regardless of compilers not complaining, is this code ill-formed?

 5  171  5
1 Jan 1970

Solution

 3

There are specific circumstances when (class) templates are implicitly instantiated: see [temp.inst], although in some cases it can vary by implementation. In no case does merely mentioning a template-id cause instantiation; typically it occurs when the type is required to be complete.

In C++20, it is possible to declare a template with constraints that do take effect even when the template specialization is merely named:

template<class T> requires !std::is_void_v<T>
struct foo {…};
2024-07-06
Davis Herring

Solution

 2

[res.on.functions]/2.5 states that the behaviour is undefined if you use an incomplete type as a template argument "when instantiating a template component or evaluating a concept, unless specifically allowed for that component" in the standard library.

void is an incomplete type ([basic.types.general]/5), and std::optional is not one of the standard library templates that allows incomplete types ([optional.optional.general]/3). So, if you instantiate std::optional<void>, the program may fail to compile or it may compile and behave unpredictably at runtime (but in practice, the first thing is what happens).

However, doing something like this

using T = std::optional<void>;

does not instantiate std::optional<void>. According to [temp.inst]/2 and [temp.inst]/11, a class template specialization is implicitly instantiated only when it is used in a way that requires the specialization to be complete, or when the completeness of the specialization affects the semantics of the program. When you merely mention the name of a class template specialization or declare an alias for it, the class template specialization doesn't need to be complete, nor is there any difference between declaring an alias to a complete or incomplete type. So in this case we do not get an implicit instantiation.

The standard library does not have a corresponding rule for non-instantiating uses of class template specializations. Indeed, if U is an incomplete class type then it is useful to be able to do something like this:

class U;

std::optional<U> getU();

class U { /* ... */ };
// U is now complete

std::optional<U> getU() {
    // define the function
}

Using a type as a return type doesn't require it to be complete until the function is actually defined, so it's legal to use std::optional<U> as a return type on a non-defining declaration prior to U being completed.

If there were any rule specifically disallowing non-instantiating uses of std::optional<void> in particular, it would need to be found in the description of std::optional itself. Since std::optional is defined as

namespace std {
  template <class T>
  class optional {
      // ...
  };

  // (deduction guide omitted)
}

nothing bad will happen when you merely pass void to the template parameter T without instantiating the class template, but as Davis Herring points out, there could be some class templates where merely mentioning the name of a specialization would be invalid even without instantiation. std::optional just isn't one of them.

2024-07-06
Brian Bi