什么是decltype(0 + 0)?

14

(受一个答案的启发。)

根据N3290,§7.1.6.2p4,其中列表项未编号,但在此处为方便起见进行了编号:

由decltype(e)表示的类型定义如下:

  1. 如果e是未括在括号中的id表达式或未括在括号中的类成员访问(5.2.5),则decltype(e)是由e命名的实体的类型。如果没有这样的实体,或者e命名一组重载函数,则程序是非法的;
  2. 否则,如果e是xvalue,则decltype(e)是T&&,其中T是e的类型;
  3. 否则,如果e是lvalue,则decltype(e)是T&,其中T是e的类型;
  4. 否则,decltype(e)是e的类型。

decltype(0 + 0)指定的类型是什么?

第1项不适用,第2项可能适用,但如果不适用,则第3项不适用,第4项将是结果。那么,什么是xvalue,0 + 0是xvalue吗?

§3.10p1:

xvalue(“eXpiring”值)也是引用对象,通常接近其生命周期的末尾(以便移动其资源,例如)。xvalue是涉及rvalue引用(8.3.2)的某些类型的表达式的结果。

我在§8.3.2中没有发现任何对此有帮助的内容,但我知道“0 + 0”不涉及任何rvalue引用。字面值0是一个prvalue,“不是xvalue的rvalue”(§3.10p1)。我相信“0 + 0”也是一个prvalue。如果是这样,“decltype(0 + 0)”将是int(而不是int&&)。

我是否在我的解释中错过了什么?这个代码是否有效?

decltype(0 + 0) x;  // Not initialized.

该代码可在GCC 4.7.0 20110427和Clang 2.9(trunk 126116)上编译。如果decltype指定了int&&类型,则其将不符合格式要求。


3
我认为你的推理没有问题。我也相信 decltype(0 + 0) 应该是 int - CB Bailey
1
值得一提的是,肯定的好答案应该是扩展xvalue的定义并展示它可以和不能做什么。(我会觉得这非常有帮助。)我需要看到如何重新表述以便专注于“什么是xvalue?”同时仍考虑“0 + 0”的具体情况。 - Fred Nurk
3
尽管最新的草案对许多表达式(包括后缀递增等)都指定了“值类别”,并且3.10中的一条说明表明条款5应该显示每个内置运算符的值类别,但除非我的搜索能力失败了,否则该草案似乎没有提到5.6到5.15之间任何二元运算符的值类别。 - CB Bailey
不考虑规范的内容,0 + 0 的意图是一个 prvalue。lvalue = identity 而且不可移动。xvalue = identity 而且可移动。prvalue = 没有 identity 而且可移动。xvalue 是指引用对象的表达式(在 C++ 中,对象具有唯一的身份,由地址、类型和生命周期确定),并且该对象可以被移动(被视为即将过期)。当然,这是我的愚蠢解释,不能在规范中找到。 - Johannes Schaub - litb
@JohannesSchaub-litb:RValue 可以是 xvalue;那么什么时候 RValue 有身份标识?(我确定这是我忽略的一个简单问题,但我很好奇,就是看不到答案。) - Fred Nurk
2
@Fred 当rvalue是xvalue时,它们具有身份。例如:int a; (int&&)a; 强制转换产生的xvalue指的是一个对象。另一个例子(int&&)2;,被引用绑定的临时变量具有身份。它的生命周期将在完整表达式结束时结束。非类、非数组的prvalue没有身份。例如 2,它与代码中出现的另一个 21+1 没有区别。 - Johannes Schaub - litb
5个回答

10

0 + 0 是一个由两个 prvalue 组成的表达式 (参考 n3290 par. 3.10),它应用了内置的 operator+,根据13.6/12,该操作符是 LR operator+(L,R),因此它返回的是一个非引用类型的函数。因此,这个表达式的结果也是一个 prvalue(参见 3.10)。

因此,0 + 0 的结果是一个 prvalue,而 0 是一个 int,因此 0 + 0 的结果也是一个 int


2
§13.6p9是一元运算符+。你需要的是§13.6p12。然而,草案中说:“这些候选函数参与操作符重载解析过程,如13.3.1.2所述,并且不用于任何其他目的。”(§13.6p1)我认为“不用于任何其他目的”意味着我们不能使用它们来确定值类别。无论如何,对于一个好答案加1分。 - Fred Nurk
4
内置运算符不是函数调用。因此,您不能执行13.6/12并获取这些候选项的所述返回类型,并将其应用回第5条款。只有在重载决策(如果至少一个操作数为类或枚举类型)时,13.6的这些候选者才是活动和相关的。它们仅用于转换类类型操作数。完成后,控制权完全交回第5条款。那个“LR operator+”并不是某种被调用的实际函数。 - Johannes Schaub - litb
@JohannesSchaub-litb 我同意,但这是(草案)标准中对问题的最接近回答。事实上,正如@Fred在聊天中提到的那样,第3.10条款承诺第5条将提供语义,而第5条却违背了这一承诺。 我们最接近填补语义漏洞的方法是将内置运算符视为函数调用,在第13条中考虑它们的原型作为它们的定义(尽管草案说这些候选函数仅用于重载决议)。然后从那里推理。 - rlc
@JohannesSchaub-litb:更重要的是,对于这个讨论,“仅用于其他目的”通知明确禁止使用§13.6p12来回答这个问题。 - Fred Nurk
但是... 返回类型和值类别不总是由重载解析确定吗? - Ben Voigt
@BenVoigt 可能是这样,但这并不意味着内置运算符的结果可以被解释为函数调用的返回值 - 这保持了我的推理漏洞(以及标准中的语义漏洞)... - rlc

5

这肯定是一个整数:

#include <iostream>
#include <typeinfo>

template<typename T>
struct ref_depth
{
        enum { value = 0 };
};

template<typename T>
struct ref_depth<T&>
{
        enum { value = 1 };
};

template<typename T>
struct ref_depth<T&&>
{
        enum { value = 2 };
};

int main() {

  std::cout
    << "int: " << typeid(int).name() << "\n"
       "decltype(0 + 0): " << typeid(decltype(0 + 0)).name() << "\n"
       "int&&: " << typeid(int&&).name() << "\n";
  std::cout 
    << "ref_depth: int: " << ref_depth<int>::value << "\n"
       "ref_depth: decltype(0 + 0): " << ref_depth<decltype(0 + 0)>::value << "\n"
       "ref_depth: int&&: " << ref_depth<int&&>::value << "\n";

}

输出:

int: i
decltype(0 + 0): i
int&&: i
ref_depth: int: 0
ref_depth: decltype(0 + 0): 0
ref_depth: int&&: 2

2
请问您能否在答案中包含代码呢?而不是仅总结代码的结论并链接到完全不同的网站。 - Fred Nurk
1
@Fred Nurk:我已经自作主张了。 - Jon Purdy

2
从5.19 [expr.const] 开始,每个文字常量表达式都是一个 prvalue。
“文字常量表达式”是指字面类型的 prvalue 核心常量表达式,但不包括指针类型。而“整数常量表达式”则是指整数或未作用域枚举类型的字面常量表达式。
因此,规则4适用于所有文字常量表达式。

谢谢,尽管我仍然看到语义上的漏洞(正如查尔斯在问题的评论中指出的那样),“int n = 42; decltype(0 + n)” :( - Fred Nurk
@Fred: 当然,尽管标准明确指出0 + 0核心常量表达式,但其prvalue状态仍然存在疑问。显然,如果它不是prvalue,它就不能是整数常量表达式,会导致许多问题。 - Ben Voigt
2
你搞反了:那段话是在定义“字面常量表达式”这个术语。 - Richard Smith
@Richard:那段确实是定义。定义提供必要条件,并可用于论述所述项目的属性。没有什么倒退的。 - Ben Voigt
-2/2:同意@Richard的观点。您没有展示出0 + 0是一个prvalue。虽然您的回答本身并没有错,但似乎与问题无关。 - Johannes Schaub - litb
显示剩余2条评论

2
你的推理是正确的。仅包含常量的表达式本身就是一个常量。因此:
decltype(0 + 0) x;

equals

decltype(0) x;

等于

int x;

1
这并没有回答问题。使用零是一个占位符,一个好的答案将展示如何推断出decltype(some_int + another_int)(或者展示为什么零字面量是特殊的)。 - Fred Nurk

0

GCC提示 int-

代码:

#include <iostream>
#include <typeinfo>

int
main ()
{
  int n;
  decltype(0 + 0) x;
  std::cout << "Type of `n': " << typeid(n).name() << std::endl;
  std::cout << "Type of `x': " << typeid(x).name() << std::endl;
}

输出:

i

i

编辑:根据第4点,这是有意义的,但我不能确定第2点是否实际生效。从我所知道的来看,0 + 0被计算为0,而0的类型是int,因此这是声明的类型。


这是正确的。请注意,在GCC中,int&&与int相同。毕竟,对int的引用就是int。毕竟,引用在理论上只是一个别名。尝试if (typeid(int) == typeid(int&&)) { std::cout << "int == int&&" << std::endl; }。即使它们的字符串相同,除非它们被完全相同地处理,否则实际的typeid也不会相同。这让我想知道底层发生了什么......目前,我会说decltype(0 + 0)的结果是类型int - user539810

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接