Различия auto и auto&& внутри диапазонного for цикла

1,00
р.
Какая разница между 1 и 2? Когда что использовать?
for (auto i : container){} // 1 for (auto&& i : container){} // 2

Ответ
Когда у вас имеется объявление вида
auto &&x = initializer
то данная rvalue ссылка &&x является особенной и носит название forwarding reference. Если инициализатор представляет собой lvalue, то эта ссылка принимает тип lvalue ссылки.
Из стандарта C++ (Document Number: N4296, 7.1.6.4 auto specifier)

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

И далее (14.8.2.1 Deducing template arguments from a function cal)

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

Что это означает? Это означает, что следующие два объявления будут эквивалентными
int x = 0 auto &&r = x
и
int x = 0 auto &r = x
Ниже приведена демонстрационная программа
#include
int main() { { int x = 0
std::cout << "x = " << x << std::endl <br> auto &&r = x
r = 10
std::cout << "x = " << x << std::endl }<br> { int x = 0
std::cout << "x = " << x << std::endl <br> auto &r = x
r = 10
std::cout << "x = " << x << std::endl } } <br>Ее вывод на консоль
x = 0 x = 10 x = 0 x = 10
Из этого также следует, что когда вы имеете дело с for предложением на основе диапазона, и соответствующий итератор, возвращаемый функцией begin, или соответствующий указатель, после применения оператора разыменования возвращают lvalue значение исходного объекта (указатели всегда возвращают lvalue значение после разыменования), то вы можете изменить этот исходный объект используя объявление forwarding reference.
Например,
#include #include
int main() { { int a[] = { 0 }
std::cout << "a[0] = " << a[0] << std::endl <br> auto &&r = *a
r = 10
std::cout << "a[0] = " << a[0] << std::endl }<br> { std::vector v(1)
std::cout << "v[0] = " << v[0] << std::endl <br> auto &&r = *v.begin()
r = 10
std::cout << "v[0] = " << v[0] << std::endl } } <br>Вывод программы на консоль:
a[0] = 0 a[0] = 10 v[0] = 0 v[0] = 10
Поэтому, например, для стандартных контейнеров, которые возвращают из разыменованного итератора ссылку на исходный объект, два подобных объявления for эквиваленны
std::vector v { 1, 2, 3, 4, 5 }
for ( auto &&x : v ) x *= 2
и
std::vector v { 1, 2, 3, 4, 5 }
for ( auto &x : v ) x *= 2
Так как в обоих случаях имеется выведенный тип переменной x как int &.
Когда же используется объявление вида
auto x = initializer
то тип переменной x выводится из спецификаторов типа инициализатора, игнорируя ссылки. То есть если даже инициализатор - это ссылочный тип, как, например, int &, тип переменной x будет int, а, следовательно вы не сможете изменить исходный объект, используя эту переменную. Например,
#include #include
int main() { { std::vector v{ 1, 2, 3, 4, 5 }
for (int x : v) std::cout << x << ' ' std::cout << std::endl <br> for (auto &&x : v) x *= 2
for (int x : v) std::cout << x << ' ' std::cout << std::endl }<br> std::cout << std::endl <br> { std::vector v{ 1, 2, 3, 4, 5 }
for (int x : v) std::cout << x << ' ' std::cout << std::endl <br> for (auto x : v) x *= 2
for (int x : v) std::cout << x << ' ' std::cout << std::endl } } <br>Сравните вывод двух кодовых блоков этой программы
1 2 3 4 5 2 4 6 8 10
1 2 3 4 5 1 2 3 4 5
Если же forwarding reference инициализируется rvalue значением, а не lvalue значением, то rvalue ссылка не будет превращаться в lvalue ссылку. Ниже показано это различие.
#include
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 <br> x = 10
std::cout << "x = " << x << std::endl std::cout << "f() = " << f() << std::endl }<br> std::cout << std::endl <br> { auto &&x = g()
std::cout << "x = " << x << std::endl <br> x = 10
std::cout << "x = " << x << std::endl std::cout << "g() = " << g() << std::endl } } <br>Вывод программы на консоль
x = 0 x = 10 f() = 0
x = 0 x = 10 g() = 10
Что касается вопроса,
Когда что использовать?
то лучше использовать auto & если вы хотите изменять значения в цикле, на которые будет ссылаться ссылка, либо const auto &, когда вы используете значения только для чтения. auto имеет смысл использовать для фундаментальных типов, например, арифметических типов или указателей), когда копирование объекта в создаваемую локальную переменную не является ресурсо-затратной операцией и не требуется изменить исходные объекты.