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?
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 {…};
[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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With