18

I'm trying to come up a simple example for an operation that results in a rvalue.

This test case should have worked, but surprisingly (to me), the result of adding two ints is not an rvalue (reference). What am I missing here?

void test(int i, int j)
{
    // this assert should pass, but fails:
    static_assert(std::is_same<decltype(i + j), int&&>(), "i + j should be a rvalue"); 
    // this assert passed, but should fail:
    static_assert(std::is_same<decltype(i + j), int>(), "this assert should fail...");
}
M.M
  • 138,810
  • 21
  • 208
  • 365
anderas
  • 5,744
  • 30
  • 49

2 Answers2

34

i + j is a prvalue expression,

A prvalue ("pure rvalue") expression is an expression that does not have identity and can be moved from.

a + b, a % b, a & b, a << b, and all other built-in arithmetic expressions;

not a xvalue,

An xvalue ("expiring value") expression is an expression that has identity and can be moved from.

And decltype specifier yields T for prvalue, not T&&.

a) if the value category of expression is xvalue, then decltype yields T&&;
b) if the value category of expression is lvalue, then decltype yields T&;
c) if the value category of expression is prvalue, then decltype yields T.

You can make it xvalue by std::move:

static_assert(std::is_same<decltype(std::move(i + j)), int&&>(), "std::move(i + j) is a xvalue then this static_assert won't fail"); 
Community
  • 1
  • 1
songyuanyao
  • 169,198
  • 16
  • 310
  • 405
  • 1
    The paragraph about `decltype` is what I missed in the more general case. (Or, more generally speaking, the difference between the *value categories* and what *references they bind to*.) Thank you! – anderas Apr 21 '16 at 08:11
  • I clarified the question a bit now that I know what I got wrong. The difference is basically *is a rvalue* vs *binds to a rvalue reference*. I was expecting to check the second one, but did something else. Would you want to add that to your answer, or should I write my own self-accepted answer? (Since that would be the solution of my actual problem.) – anderas Apr 21 '16 at 08:22
  • @anderas I think it's fine to write your answer, based on your own aspect and understanding. – songyuanyao Apr 21 '16 at 08:24
  • Done! Not sure which answer should be the accepted one, though. Yours answers the question *as written*, while mine answers the underlying problem. – anderas Apr 21 '16 at 08:40
  • @anderas Fine, it's up to you, op. – songyuanyao Apr 21 '16 at 08:52
5

Based on @songyuanyao's Answer, I noticed that my mistake was checking the wrong thing: My intention was to check whether the result of i+j would bind to a rvalue reference, but I checked whether it is a rvalue reference.

decltype deduces the type based on the value category, not based on what reference type the value would bind to:

1) if the value category of expression is xvalue, then decltype yields T&&;
2) if the value category of expression is lvalue, then decltype yields T&;
3) if the value category of expression is prvalue, then decltype yields T.

As shown in the list, since C++11, rvalues don't exist as a distinct category on the lowest level. They are now a composite category containing both prvalues as well as xvalues. The question as written asks whether the expression is a rvalue reference and checks whether it is an xvalue.

From the list above, it is clear that i+j is a prvalue, so the third case applies. This explains why decltype(i + j) is int and not int&&. Both xvaluesand prvalues bind to rvalue references.

So by checking for whether i+j binds to a lvalue reference or a rvalue reference confirms that, indeed, it binds to a rvalue reference:

void foo(const int& f)
{
    std::cout << "binds to lvalue reference" << std::endl;
}

void foo(int&& f)
{
    std::cout << "binds to rvalue reference" << std::endl;
}

void test(int i, int j)
{
    foo(i); // lvalue -> lvalue ref

    foo(std::move(i)); // xvalue -> rvalue ref 
    // (std::move converts the argument to a rvalue reference and returns it as an xvalue)

    foo(i + j); // prvalue -> rvalue ref
}

In conclusion: i+j is not a rvalue reference, but it binds to one.

Community
  • 1
  • 1
anderas
  • 5,744
  • 30
  • 49
  • Can you make an example that is able to fetch all three types? – Chiel Apr 21 '16 at 08:58
  • @Chiel, no, and that is the point that I tried to make with the distinction between the *category* and *what a value binds to*: There are only `rvalue` and `lvalue` references (not `xvalue`, `lvalue` and `prvalue` references). `prvalues` AND `xvalues` bind to rvalue references. – anderas Apr 21 '16 at 09:06
  • @Chiel unless you wanted me to show what values of these categories bind to. I added that to the sample. – anderas Apr 21 '16 at 09:11
  • Thanks. Can you explain me exactly what the `std::move` does here and what that means to `i`? – Chiel Apr 21 '16 at 09:16
  • 1
    @Chiel in this case, `std::move` does the same thing as always: It casts the argument to a rvalue reference and returns it as an `xvalue` (which in turn binds to a rvalue reference, causing the `int&&` overload to be selected. – anderas Apr 21 '16 at 09:19
  • "In the new standard, there are no rvalues per se," - huh? You claim rvalues are being removed from C++17? – M.M Apr 22 '16 at 05:07
  • @M.M what was called `rvalue` in C++ before C++11 is now a `prvalue`. `rvalue`s still exist, but not as a distinct value category. They are a now a set that contains `prvalue`s and `xvalue`s. See http://en.cppreference.com/w/cpp/language/value_category – anderas Apr 22 '16 at 05:10
  • Yes, so there are rvalues. `i + j` is an rvalue. (It's also a prvalue, but that doesn't mean it isn't an rvalue). – M.M Apr 22 '16 at 05:16
  • Right, but it being an rvalue doesn't mean it is automatically a prvalue. (The implication would work the other way around.) I hope the revision made clear what I meant to say. – anderas Apr 22 '16 at 05:18
  • Not really, it still says "there are no rvalues per se" which is false. rvalue is one of the 5 value categories (which happens to overlap with other categories). – M.M Apr 22 '16 at 05:20
  • We wouldn't say "there are no numbers per se" just because the categories "even number" and "odd number" exist – M.M Apr 22 '16 at 05:21