为啥 'typeid(x) == typeid(y)' 评估为真,其中 'x' 和 'y' 分别是 T 和 T& 类型的 id 表达式?

Posted

技术标签:

【中文标题】为啥 \'typeid(x) == typeid(y)\' 评估为真,其中 \'x\' 和 \'y\' 分别是 T 和 T& 类型的 id 表达式?【英文标题】:Why does 'typeid(x) == typeid(y)' evaluate to true, where 'x' and 'y' are id-expression of type T and T& respectively?为什么 'typeid(x) == typeid(y)' 评估为真,其中 'x' 和 'y' 分别是 T 和 T& 类型的 id 表达式? 【发布时间】:2020-09-19 20:43:05 【问题描述】:

我正在阅读 C++11 标准草案,关于 [expr.typeid] 的部分提到以下内容(强调我的):

[...]

当 typeid 应用于多态类类型的 glvalue 以外的表达式时,结果是指 到表示表达式的静态类型的 std::type_info 对象。左值到右值(4.1),数组到指针 (4.2) 和函数到指针 (4.3) 的转换不适用于表达式。如果类型 表达式是一个类类型,这个类应该是完全定义的。表达式是未计算的操作数 (第 5 条)。

当 typeid 应用于 type-id 时,结果引用一个 std::type_info 对象,表示其类型 类型 ID。 如果 type-id 的类型是对可能是 cv 限定类型的引用,则 typeid 的结果 表达式引用代表 cv 非限定引用类型的 std::type_info 对象。 如果 type-id 是类类型或者类类型的引用,类应该是完全定义的。

在同一部分的 p5 中,它继续给出以下示例:

class D  /* ... */ ;
D d1;
const D d2;

typeid(d1) == typeid(d2); // yields true
typeid(D)  == typeid(const D); // yields true
typeid(D)  == typeid(d2); // yields true
typeid(D)  == typeid(const D&); // yields true   -- (1)

给定以下代码示例:

int main()

    int foo = 42;
    int &bar = foo;
    bool comp1 = (typeid(int) == typeid(int&));    // Yields true, same as (1)   -- (2) 
    bool comp2 = (typeid(foo) == typeid(bar));     // Yields true, Why?          -- (3)

我的理解是 [expr.typeid]p4 只讨论 typeid(type-id)bar 中的形式typeid(bar)id-expression 而不是 type-id。为什么上面的 (3) 评估为true?标准中的哪些文本涵盖了这一点?我错过了什么?

【问题讨论】:

typeid 不关心常量或引用。对于typeid,它仍然是同一类型。 你的问题有答案。 如果 type-id 的类型是对可能有 cv 限定的类型的引用,则 typeid 表达式的结果是指一个 std::type_info 对象,该对象表示 cv 不限定的引用类型。 意味着你得到的是被引用事物的 typeid,而不是引用本身。 注意它说的是referenced type.,而不是reference type @NathanOliver 文本说 当 typeid 应用于 type-id 时,我试图弄清楚它在哪里说“当 typeid应用于 glvalue 表达式...结果引用了一个 std::type_info 对象,表示 cv-unqualified 引用类型"? @NathanOliver - 这不是问题的答案。 bar 不是 type-id,段落不适用 【参考方案1】:

答案在[expr]

5 如果一个表达式最初的类型是“对 T 的引用”([dcl.ref], [dcl.init.ref]),类型被调整为 T 之前的任何进一步 分析。表达式指定由表示的对象或函数 引用,并且表达式是左值或 xvalue,具体取决于 关于表达式。

所以当我们进入 [expr.typeid]

3 当 typeid 应用于除 a 的 glvalue 之外的表达式时 多态类类型,结果引用一个 std::type_info 对象 表示表达式的静态类型。左值到右值 ([conv.lval])、数组到指针 ([conv.array]) 和 函数到指针 ([conv.func]) 的转换不适用于 表达。如果表达式的类型是类类型,则该类 应完全定义。表达式是未计算的操作数 (子句 [expr])。

typeid 检查时,有问题的 id 表达式已经是引用类型。

【讨论】:

我可能在吹毛求疵,但在typeid(bar)bar 不是子表达式吗? [expr] 中的文本在哪里谈论表达式。 @Cheshar - 你做出了任意的区分。所有表达式都受规则的约束,无论它们是完整表达式还是其子表达式。 但是标准不应该明确说明吗?所有表达式(完整或子)都受此规则的约束?另一方面,查看 [intro.execution]p10 完整表达式是不是另一个表达式的子表达式的表达式。 [ 注意:在某些情况下,例如未计算的操作数,句法子表达式被视为完整表达式(第 5 条)。 ——尾注]这可以解释这种特殊情况,因为 typeid 的操作数对于非多态左值类型是不求值的? @Cheshar - [intro.execution] 所描述的意义上的完整表达式不必是 [expr] 意义上的表达式。从段落S s2 = 2; 中的非常示例来看,是“完整表达式”,但不是[expr] 下的表达式。介绍中的措辞更多地涉及排序。我引用的规则处理[expr]. 下描述的所有表达式 在这个sn-p int a = 1; int& b = 1; a + b; 中,id-表达式b 的类型是int,它是用于确定含义的类型[expr.add] 中的表达式(即,如果操作数是算术类型)。 换句话说,没有子表达式存在于真空中。它的类型和值类别有助于确定基于它的其他表达式的含义。这就是为什么该规则也适用于子表达式的原因。【参考方案2】:

C++ 的大部分表达“系统”都是这样工作的。您只观察到贯穿整个语言的规则的一个示例:在表达式的上下文中,引用有时并不是真正的“事物”;它们只是以与原始声明名称相同的方式引用现有对象。它们旨在在某些层上“透明”,这在某些内部工作中很明显。

在某些地方可能看起来违反直觉;例如,虽然std::move(expr) 返回一个T&&,但结果表达式是一个右值T不是一个T&&)……事实上,这个表达式是一个右值,它允许它当您稍后将其传递给某个函数时绑定到右值引用参数。 (有一个常见的误解是匹配类型 T&& 使这项工作有效。)

typeid 可以说是另一个反直觉的例子。您缺少的特定规则是[expr.type],负责在任何其他处理之前在表达式中“衰减”这些引用类型。此时,表达式的值类别具有重要意义,而这个值类别至少部分地由原始的、未调整的类型决定。这就是表达式的类型和值类别随着数据在程序中移动而演变的方式。

const 的后续剥离是您已经引用的 the rules for typeid 的一部分。)

typeid 传递类型 are also distinct 时的规则)。

通常,我们不必担心这一点。不幸的是,有几个地方可以观察到它,感觉有点像抽象泄漏。你找到了其中一个地方。不过,这些规则确实让一切最终都走到了一起。

【讨论】:

除了[expr.type]/1 之外,可能还有其他内容。它没有解释剥离 const 性,以及即使传递类型而不是表达式也会忽略引用性这一事实。 @HolyBlackCat That's right(OP 已经引用了) @HolyBlackCat 是的,采用has its own distinct rules 类型的变体

以上是关于为啥 'typeid(x) == typeid(y)' 评估为真,其中 'x' 和 'y' 分别是 T 和 T& 类型的 id 表达式?的主要内容,如果未能解决你的问题,请参考以下文章

为啥非多态 typeid 需要 RTTI?

C++-typeid-操作符

typeinfo / typeid 输出

C++ 强制转换和 typeid

什么时候使用“typeid”是最好的解决方案?

在 `typeid` 代码中奇怪地使用`?:`