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>>);
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?
[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.