14

(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.

Community
  • 1
  • 1
Fred Nurk
  • 13,952
  • 4
  • 37
  • 63
  • 3
    I don't see anything wrong with your reasoning. I believe that `decltype(0 + 0)` should be `int`, too. – CB Bailey May 08 '11 at 00:36
  • 1
    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 May 08 '11 at 00:43
  • 3
    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 May 08 '11 at 00:58
  • 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 May 08 '11 at 01:19
  • @JohannesSchaub-litb: Rvalues can be xvalues; when do rvalues have identity? (I'm sure it's something simple I'm overlooking, but I'm curious and can't see it.) – Fred Nurk May 08 '11 at 03:12
  • 2
    @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 May 08 '11 at 05:19

5 Answers5

10

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

rlc
  • 2,808
  • 18
  • 23
  • 2
    §13.6p9 is *unary* operator+. You want §13.6p12. However, the draft says "These candidate functions participate in the operator overload resolution process as described in 13.3.1.2 and are used for no other purpose." (§13.6p1) I believe "no other purpose" means we can't use them to determine value category. +1 for a good answer anyway. – Fred Nurk May 08 '11 at 02:59
  • 4
    the built-in operators are not function calls. So you cannot take 13.6/12 and get the described return type of those candidates and apply them back to clause 5. Those candidates of 13.6 are only active and relevant when you do overload resolution (if at least one operand is of class or enumeration type). And they are only used for converting class type operands. After that is done, control is completely given back to clause 5. That "LR operator+" is not an actual function that is somehow called. – Johannes Schaub - litb May 12 '11 at 05:02
  • @JohannesSchaub-litb I agree, but it's the closest thing in the (draft) standard to an answer to the question. Fact is that, as @Fred mentioned in chat, clause 3.10 promises that clause 5 will provide the semantics, and clause 5 breaks that promise. The closest thing we then have to something to plug the semantic hole is to consider the built-in operators as function calls, consider their prototypes in clause 13 as their definitions (despite the fact that the draft says that those candidate functions are used for no other purpose than overload resolution). and reason on from there. – rlc May 12 '11 at 13:24
  • @JohannesSchaub-litb: More importantly for this discussion, the "used for no other purpose" notice explicitly disallows using §13.6p12 for the purpose of answering this question. – Fred Nurk May 13 '11 at 01:18
  • But... are return type and value category not always determined by overload resolution? – Ben Voigt Jun 03 '11 at 02:04
  • @BenVoigt maybe, but that doesn't mean that the result of a **built-in** operator can be construed as a return value from a function call - which maintains the hole in my reasoning (and the semantic hole in the standard)... – rlc Jun 03 '11 at 17:22
5

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
Khaled Nassar
  • 884
  • 8
  • 23
2

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;
orlp
  • 112,504
  • 36
  • 218
  • 315
  • 1
    This doesn't answer the question. Using zeros is a placeholder, and a good answer will show how to extrapolate to decltype(some_int + another_int) (or show why zero literals are special). – Fred Nurk May 10 '11 at 09:14
2

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.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • Thanks, though I still see a hole in the semantics (as pointed out by Charles in a comment on the question) for "int n = 42; decltype(0 + n)". :( – Fred Nurk Jun 03 '11 at 02:00
  • @Fred: And of course, although the standard makes it clear that `0 + 0` is a *core constant expression*, its *prvalue* status remains in doubt. Obviously if it weren't a *prvalue* it couldn't be an *integral constant expression* and a LOT of things would break. – Ben Voigt Jun 03 '11 at 02:02
  • 2
    You have this backwards: that paragraph is **defining** the term *literal constant expression*. – Richard Smith Mar 21 '12 at 06:40
  • @Richard: That paragraph is indeed a definition. A definition provides necessary conditions, and can be used to argue about the properties of said items. There's nothing backwards. – Ben Voigt Mar 21 '12 at 13:50
  • @BenVoigt You appear to be saying that `0 + 0` is a prvalue *because* it is a literal constant expression. However, in order to determine that `0 + 0` is a literal constant expression, we must first determine whether it is a prvalue, which is begging the question. Whether an expression is a constant expression does not affect its value category; a useful answer would refer to 3.10 or 5/6, which explain which expressions are xvalues. – Richard Smith Mar 22 '12 at 07:35
  • @RichardSmith: That's what I said in my comment above, isn't it? – Ben Voigt Mar 22 '12 at 16:01
  • -2/2: agreed with @Richard. you have not shown that `0 + 0` is a prvalue. while your reply is not wrong in itself, it appears to be insignificant to the question – Johannes Schaub - litb Feb 21 '17 at 09:43
0

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.

  • That is true. Note that with GCC, int&& is the same as int. After all, a reference to an int IS an int. After all, a reference is only an alias in theory. Try `if (typeid(int) == typeid(int&&)) { std::cout << "int == int&&" << std::endl; }`. Even if the strings for them are the same, the actual typeid wouldn't be the same unless they're treated EXACTLY the same. This makes me wonder what's going on underneath it all... For now, I'd say that `decltype(0 + 0)` results in type `int`. –  May 08 '11 at 01:11