c++ – Differences between auto and auto && inside a for loop

Question:

What's the difference between 1 and 2? When to use what?

for (auto   i : container){} // 1
for (auto&& i : container){} // 2

Answer:

When you have a declaration like

auto &&x = initializer;

then the given rvalue reference &&x is special and is called the forwarding reference . If the initializer is an lvalue , then this reference takes the type of the lvalue reference.

From the C ++ standard (Document Number: N4296, 7.1.6.4 auto speci fi er)

  1. … Deduce a value for U using the rules of template argument deduction from a function call (14.8.2.1) , where P is a function template parameter type and the corresponding argument is the initializer, or L in the case of direct-list -initialization.

And further (14.8.2.1 Deducing template arguments from a function cal)

  1. … A forwarding reference is an rvalue reference to a cv-unqualified template parameter. If P is a forwarding reference and the argument is an lvalue, the type “lvalue reference to A” is used in place of A for type deduction

What does this mean? This means that the following two declarations will be equivalent

int x = 0;
auto &&r = x;

and

int x = 0;
auto &r = x;

Below is a demo program

#include <iostream>

int main()
{
    { 
        int x = 0;

        std::cout << "x = " << x << std::endl;

        auto &&r = x;

        r = 10;

        std::cout << "x = " << x << std::endl;
    }

    {
        int x = 0;

        std::cout << "x = " << x << std::endl;

        auto &r = x;

        r = 10;

        std::cout << "x = " << x << std::endl;
    }
}

Its output to the console

x = 0
x = 10
x = 0
x = 10

It also follows that when you are dealing with a for clause based on a range, and the corresponding iterator returned by the begin function, or the corresponding pointer, after applying the dereference operator, return the lvalue value of the original object (pointers always return an lvalue value after dereferencing), then you you can change this original object using the forwarding reference declaration.

For instance,

#include <iostream>
#include <vector>

int main()
{
    {
        int a[] = { 0 };

        std::cout << "a[0] = " << a[0] << std::endl;

        auto &&r = *a;

        r = 10;

        std::cout << "a[0] = " << a[0] << std::endl;
    }

    {
        std::vector<int> v(1);

        std::cout << "v[0] = " << v[0] << std::endl;

        auto &&r = *v.begin();

        r = 10;

        std::cout << "v[0] = " << v[0] << std::endl;
    }
}

Output of the program to the console:

a[0] = 0
a[0] = 10
v[0] = 0
v[0] = 10

Therefore, for example, for standard containers that return a reference to the original object from a dereferenced iterator, two similar for declarations are equivalent

std::vector<int> v { 1, 2, 3, 4, 5 };

for ( auto &&x : v ) x *= 2;

and

std::vector<int> v { 1, 2, 3, 4, 5 };

for ( auto &x : v ) x *= 2;

Since in both cases there is the inferred type of the variable x as int & .

When is a declaration like

auto x = initializer;

then the type of the variable x is inferred from the type specifiers of the initializer, ignoring references. That is, even if the initializer is a reference type, such as int & , the type of the variable x will be int , and therefore you cannot modify the original object using this variable. For instance,

#include <iostream>
#include <vector>

int main()
{
    {
        std::vector<int> v{ 1, 2, 3, 4, 5 };

        for (int x : v) std::cout << x << ' ';
        std::cout << std::endl;

        for (auto &&x : v) x *= 2;

        for (int x : v) std::cout << x << ' ';
        std::cout << std::endl;
    }

    std::cout << std::endl;

    {
        std::vector<int> v{ 1, 2, 3, 4, 5 };

        for (int x : v) std::cout << x << ' ';
        std::cout << std::endl;

        for (auto x : v) x *= 2;

        for (int x : v) std::cout << x << ' ';
        std::cout << std::endl;
    }
}

Compare the output of the two code blocks of this program

1 2 3 4 5
2 4 6 8 10

1 2 3 4 5
1 2 3 4 5

If the forwarding reference initialized with an rvalue value rather than an lvalue value, then the rvalue reference will not become an lvalue reference. This difference is shown below.

#include <iostream>

int f()
{
    static int x;

    return x;
}

int & g()
{
    static int x;

    return x;
}

int main()
{
    {
        auto &&x = f();

        std::cout << "x = " << x << std::endl;

        x = 10;

        std::cout << "x = " << x << std::endl;
        std::cout << "f() = " << f() << std::endl;
    }

    std::cout << std::endl;

    {
        auto &&x = g();


        std::cout << "x = " << x << std::endl;

        x = 10;

        std::cout << "x = " << x << std::endl;
        std::cout << "g() = " << g() << std::endl;
    }
}

Output of the program to the console

x = 0
x = 10
f() = 0

x = 0
x = 10
g() = 10

As for the question,

When to use what?

then it is better to use auto & if you want to change the values ​​in a loop that will be referenced, or const auto & when you are using read-only values. auto makes sense for fundamental types such as arithmetic types or pointers) when copying an object to the local variable being created is not a resource-intensive operation and you do not need to modify the original objects.

Scroll to Top