5

Given the code below, everything works. How come that the variable d is reference to int? What is going on?

int main()
{
    int a= 10;
    int &&b = a+10; // b is int &&
    auto c =b+10; // c is int
    auto &&d = a; // d is int&
    //int &&di = a; // error, as expected
    return (0);
}
R. Martinho Fernandes
  • 228,013
  • 71
  • 433
  • 510
dodol
  • 1,073
  • 2
  • 16
  • 33

5 Answers5

8

This has to do with the reference collapsing rules in type deduction.

A& & becomes A&
A& && becomes A&
A&& & becomes A&
A&& && becomes A&&
bames53
  • 86,085
  • 15
  • 179
  • 244
4

There is a special rule in type deduction. In auto &&d = a; "auto&&" is an rvalue reference to a non-const non-volatile type and "a" is an lvalue, then this special rule is applied: the type of "a" is treated as int& instead of int. Then as usual choose the type of "auto" to be identical to the type of "a", that is int&. So the type of "auto&&" is int& according to reference collapsing as mentioned by bames53.

Cosyn
  • 4,404
  • 1
  • 33
  • 26
  • where in the standard is this rule specified? – je4d Apr 20 '12 at 08:23
  • 1
    First deducing the type of auto is the same as deducing template arguments from a function call (7.1.6.4/6), then this rule is in 14.8.2.1/3. – Cosyn Apr 20 '12 at 09:26
  • This is not a special rule, confusingly `&&` has entirely different semantics when applied to template arguments (and thus auto-typed variables) – jupp0r Feb 03 '17 at 22:28
4

Worth mentioning on top of the reference collapsing rules is how to force d to be an rvalue reference. You can use std::move for this:

int a =4; 
auto &&d = std::move(a); // d is type &&

Of course when talking integers, rvalue references are silly, as pass by value is just as efficient. This is useful in forcing move semantic optimizations, say if you wanted to insert a complex type at the end of a function, where that type would go out of scope...

vector<std::string> v;
void f()
{
    string s;
    foo(s); // do some kind of operation on s.
    v.push_back(std::move(s)); // use push_back( &&) instead of push_back(const &); 
}
Edward
  • 488
  • 2
  • 16
1

auto&& invokes perfect forwarding. As a is an lvalue of type int, d is an lvalue reference to int.

Puppy
  • 144,682
  • 38
  • 256
  • 465
  • ok, but auto &dr = a is ref to int too. It's just not intuitive. At least at first glance. – dodol Apr 19 '12 at 12:45
0

You need to use std::forward in conjunction with auto&& to get the behavior you most likely want. See the following example code:

#include <iostream>

template <class T>
void f(T&& x) {
    std::cout << "R-value!" << std::endl;    
}

template <class T>
void f(T& x) {
    std::cout << "L-value!" << std::endl;    
}

int get() {
    return 0;    
}

int main()
{
    // Correct usage
    {
        std::cout << "Correct usage" << std::endl;
        auto&& s = get();
        f(std::forward<decltype(s)>(s)); // Prints: R-value!
        std::cout << "----------------------------\n" << std::endl;
    }
    
    // Above code is robust to refactorings
    {
        std::cout << "After refactoring" << std::endl;
        int i = 5;
        auto&&s = i;
        f(std::forward<decltype(s)>(s)); // Prints: L-value!
        std::cout << "----------------------------\n" << std::endl;
    }
    // Forward is necessary, else l-value version is called.
    {
        std::cout << "Forgetting forward" << std::endl;
        auto&& s = get();
        f(s); // Prints: L-value!
        std::cout << "----------------------------\n" << std::endl;
    }
}

Here a link to run it yourself: http://cpp.sh/8lducx

Andreas Pasternak
  • 1,250
  • 10
  • 18