我在修改现有代码时偶然发现创建了一个别名声明std::optional<void>
。它来自如下模板代码:
using ret_t = std::invoke_result_t<Fn, Args...>;
using opt_ret_t = std::optional<ret_t>;
代码稍后会区分返回类型为 (not) 的情况void
,因此实际上从未optional
创建过对象。
为了确保我没有遗漏任何东西,我在该代码分支中添加了一个断言,并且所有内容都经过编译:
static_assert(std::is_same_v<opt_ret_t, std::optional<void>>);
没有可选的引用、函数、数组或 cv void;如果程序实例化具有这种类型的可选项,则程序格式不正确。
那么,无论编译器是否抱怨,这段代码都是错误的吗?
存在隐式实例化(类)模板的特定情况:请参阅 [temp.inst],尽管在某些情况下,它可能因实现而异。在任何情况下,仅仅提及模板 ID都不会导致实例化;通常,当类型需要完整时,才会发生这种情况。
在 C++20 中,可以声明一个带有约束的模板,即使模板特化仅仅被命名,这些约束也会生效:
[res.on.functions]/2.5指出,如果在标准库中“实例化模板组件或评估概念时,使用不完整类型作为模板参数,则行为未定义,除非该组件有特别允许”。
void
是不完整类型 ([basic.types.general]/5),并且std::optional
不是允许不完整类型的标准库模板之一 ([optional.optional.general]/3)。因此,如果您实例化std::optional<void>
,程序可能无法编译,或者它可能在编译时在运行时行为不可预测(但实际上,首先要考虑发生的事情)。然而,做这样的事情
不实例化
std::optional<void>
。根据[temp.inst]/2和 [temp.inst]/11,类模板特化仅在以需要特化完整的方式使用时,或当特化的完整性影响程序的语义时才会隐式实例化。当您仅提及类模板特化的名称或为其声明别名时,类模板特化不需要完整,声明完整或不完整类型的别名之间也没有任何区别。所以在这种情况下,我们没有得到隐式实例化。标准库没有针对类模板特化非实例化用途的相应规则。事实上,如果
U
是不完整的类类型,那么能够执行以下操作会很有用:使用类型作为返回类型并不要求它在函数实际定义之前是完整的,因此在完成
std::optional<U>
之前在非定义声明上用作返回类型是合法的。U
如果有任何规则明确禁止非实例化使用
std::optional<void>
,则需要在其自身的描述中找到它std::optional
。由于std::optional
定义为void
当您仅仅传递给模板参数T
而不实例化类模板时,不会发生任何不好的事情,但是正如 Davis Herring 指出的那样,可能存在一些类模板,其中仅仅提及专门化的名称即使没有实例化也是无效的。std::optional
只是其中之一。