幸运的是,该标准有一个方便的列表(§ 5 [expr] ¶ 8):
在某些情况下,未计算的操作数会出现(5.2.8、5.3.3、5.3.7、7.1.6.2)。未计算的操作数不会被计算。未计算的操作数被视为完整表达式。
让我们详细看看这些。
我将在我的示例中使用以下声明。声明的函数从未在任何地方定义,因此如果对它们的调用出现在 求值 上下文中,则程序格式错误,我们将收到链接时错误。但是,在未评估的上下文中调用它们是可以的。
int foo(); // never defined anywhere
struct widget
{
virtual ~widget();
static widget& get_instance(); // never defined anywhere
};
typeid
§ 5.2.8 [expr.typeid] ¶ 3:
当typeid 应用于多态类类型的glvalue 以外的表达式 时,结果引用代表表达式静态类型的std::type_info 对象。左值到右值 (4.1)、数组到指针 (4.2) 和函数到指针 (4.3) 转换不适用于表达式。如果表达式的类型是类类型,则类应该是完全定义的。 表达式是未计算的操作数
(第 5 条)。
注意多态类(class 至少有一个 virtual 成员)的突出例外。
所以没关系
typeid( foo() )
并为int 生成一个std::type_info 对象,而这
typeid( widget::get_instance() )
不是并且可能会产生链接时错误。它必须评估操作数,因为动态类型是通过在运行时查找 vptr 来确定的。
操作数的静态类型是否为多态这一事实以如此戏剧性但微妙的方式改变了运算符的语义,这让我感到非常困惑。
sizeof
§ 5.3.3 [expr.sizeof] ¶ 1:
sizeof 运算符在其操作数的对象表示中产生字节数。 操作数要么是一个表达式,它是一个未计算的操作数(第 5 条),要么是一个带括号的 type-id。 sizeof 运算符不得应用于具有函数或不完整类型的表达式、其基础类型在其所有枚举数被声明之前未固定的枚举类型、此类类型的括号名称或泛左值指定一个位域。
以下
sizeof( foo() )
完全没问题,相当于sizeof(int)。
sizeof( widget::get_instance() )
也是允许的。但是请注意,它等同于 sizeof(widget),因此在多态 return 类型上可能不是很有用。
noexcept
§ 5.3.7 [expr.unary.noexcept] ¶ 1:
noexcept 运算符确定其操作数的求值,它是一个未求值的操作数(第 5 条),是否可以抛出异常 (15.1)。
表达式
noexcept( foo() )
有效,计算结果为false。
这是一个更现实的例子,也是有效的。
void bar() noexcept(noexcept( widget::get_instance() ));
请注意,只有内部的noexcept 是运算符,而外部是说明符。
decltype
§ 7.1.6.2 [dcl.type.simple] ¶ 4.4:
decltype 说明符的操作数是未计算的操作数(第 5 条)。
声明
decltype( foo() ) n = 42;
声明int 类型的变量n 并将其初始化为值42。
auto baz() -> decltype( widget::get_instance() );
声明一个不带参数的函数baz,而returns 是widget&。
仅此而已(从 C++14 开始)。