14
votes

(Prompted by an answer.)

Given N3290, §7.1.6.2p4, where the list items are unnumbered, but numbered here for our convenience:

The type denoted by decltype(e) is defined as follows:

  1. if e is an unparenthesized id-expression or an unparenthesized class member access (5.2.5), decltype(e) is the type of the entity named by e. If there is no such entity, or if e names a set of overloaded functions, the program is ill-formed;
  2. otherwise, if e is an xvalue, decltype(e) is T&&, where T is the type of e;
  3. otherwise, if e is an lvalue, decltype(e) is T&, where T is the type of e;
  4. otherwise, decltype(e) is the type of e.

What is the type specified by decltype(0 + 0)?

Item 1 doesn't apply, 2 might, but if not, then 3 doesn't apply and 4 would be the result. So, what is an xvalue, and is 0 + 0 an xvalue?

§3.10p1:

An xvalue (an “eXpiring” value) also refers to an object, usually near the end of its lifetime (so that its resources may be moved, for example). An xvalue is the result of certain kinds of expressions involving rvalue references (8.3.2).

I don't see anything in §8.3.2 that would be helpful here, but I do know "0 + 0" doesn't involve any rvalue-references. The literal 0 is a prvalue, which is "an rvalue that is not an xvalue" (§3.10p1). I believe "0 + 0" is also a prvalue. If that's true, "decltype(0 + 0)" would be int (not int&&).

Have I missed something in my interpretation? Is this code well-formed?

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

The code compiles on GCC 4.7.0 20110427 and Clang 2.9 (trunk 126116). It would not be well-formed if the decltype specified an int&& type, for example.

5
I don't see anything wrong with your reasoning. I believe that decltype(0 + 0) should be int, too. - CB Bailey
FWIW, a good answer in the affirmative would be to expand on the definition of xvalue and show what it can and cannot be. (I would find that very helpful.) I need to see how to reword to focus on "what is an xvalue?" while still considering this concrete case of "0 + 0". - Fred Nurk
Although the latest draft does specify the value category for many expressions, including things like postfix increment and a note in 3.10 indicates that clause 5 should show the category of the value for each built-in operator, the draft doesn't seem to mention a value category for any of the binary operators from 5.6 to 5.15 unless my search powers have failed me. - CB Bailey
Disregarding of what the spec says, the intent is that 0 + 0 is a prvalue. lvalue = identity and not movable. xvalue = identity and movable. prvalue = no identity and movable. An xvalue is an expression that refers to an object (and objects in C++ have an unique identity, determined by address, type and lifetime), and that object may be moved from (is considered eXpiring). This is my silly explanation, of course not to be found in the spec. - Johannes Schaub - litb
@Fred when rvalues are xvalues, they have identity. Example: int a; (int&&)a; the xvalue the cast yields refers to an object. Another, (int&&)2;, the temporary bound by the reference has identity. Its lifetime will end at the end of the full expression. A (non-class, non-array) prvalue has no identity. Example 2, which is no different from another 2 appearing in the code, or from 1+1, etc.. - Johannes Schaub - litb

5 Answers

10
votes

0 + 0 is an expression of two prvalues, (n3290 par. 3.10) which applies the built-in operator+, which, per 13.6/12 is LR operator+(L,R), which is therefore a function that returns something that is not a reference. The result of the expression is therefore also a prvalue (as per 3.10).

Hence, the result of 0 + 0 is a prvalue, 0 is an int, therefore the result of 0 + 0 is an int

5
votes

It is definitely an int:

#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";

}

Output:

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

Your reasoning is correct. An expression involving only constants is a constant by itself. Thus

decltype(0 + 0) x;

equals

decltype(0) x;

which equals

int x;
2
votes

From 5.19 [expr.const], every literal constant expression is a prvalue.

A literal constant expression is a prvalue core constant expression of literal type, but not pointer type. An integral constant expression is a literal constant expression of integral or unscoped enumeration type.

Therefore rule 4 applies to all literal constant expressions.

0
votes

GCC says int-

Code:

#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;
}

Output:

i

i

Edit: It makes sense according to point 4, but I can't say for sure that point 2 isn't actually the one in effect. From what I can tell, 0 + 0 is evaluated to 0, and the type of 0 is int, so that is the declared type.