For auto c что это
Range-based for loop in C++ is added since C++ 11. It executes a for loop over a range. Used as a more readable equivalent to the traditional for loop operating over a range of values, such as all elements in a container.
C++ 17 or higher: Range-based loops can also be used with maps like this:
Here [key, value] works like elements of pair which can be directly accessed without specifying first or second keyword.
Petr Zemek
Links
Categories
Auto Type Deduction in Range-Based For Loops
Have you ever wondered which of the following variants you should use in range-based for loops and when? auto , const auto , auto& , const auto& , auto&& , const auto&& , or decltype(auto) ? This post tries to present rules of thumb that you can use in day-to-day coding. As you will see, only four of these variants are generally useful.
Introduction
Consider the following mapping of words into their occurrence count:
Let’s say we want to iterate over the container and modify the occurrence counts. In C++98, we could do this in the following, standard way:
As you can clearly see, this is lengthy and tedious to write. Moreover, to access the words and their occurrences, we have to use i->first and i->second , whose meaning is not obvious.
Since C++11, we can use a range-based for loop, which provides a more succinct notation:
As a side note, the const before std::string is necessary because keys in maps are immutable. Without the const , the code would fail to compile.
We may simplify the code even further by combining the range-based for loop with automatic type deduction:
The present post is dedicated to different forms of automatic type deduction and their suitability for use in day-to-day coding.
However, before we jump into that, let us briefly consider the second disadvantage of the previous approaches. As you can see, there is still a need to use the non-obvious p.first and p.second to access the words and their occurrence counts. Fortunately, in C++1z, we will be able to write this:
It uses so-called structured bindings to decompose the pairs into two components: the word and its occurrence count. As of 2016-08-17, no compiler supports this feature, although the development version of Clang has this implemented.
Automatic Type Deduction
We will go over the following type-deduction variants, one by one:
- auto
- const auto
- auto&
- const auto&
- auto&&
- const auto&&
- decltype(auto) (since C++14)
As a simplification, we will only consider the const qualifier and ignore volatile . Its use in range-based for loops is seldom, anyway.
What is important to note is that the used type-deduction variant not only provides instructions for the compiler but also signalizes an intent to the reader of your code. For example, when one sees auto& , he or she can assume that the elements of the range will be modified. If they are not modified, the reader may be puzzled and start to ask: Is there a bug in the code? Shouldn’t const auto& be used instead? Is there a hidden reason behind the absence of const ? Therefore, when using automatic type deduction, try to choose a variant that makes your intent clear.
This will create a copy of each element in the range. Therefore, use this variant when you want to work with a copy. For example, you may be iterating over a vector of strings and want to convert each string to uppercase and then pass it to a function. By using auto , a copy of each string will be provided for you. You can change it and pass forward.
The following facts need to be kept in mind when using auto :
-
Beware of containers returning proxy objects upon dereferencing of their iterators. Use of auto may lead to inadvertent changes of elements in the container. For example, consider the following example, which iterates over a vector of bools:
const auto
The use of const auto may suggest that you want to work with an immutable copy of each element. However, when would you want this? Why not use const auto& ? Why creating a copy when you will not be able to change it? And, even if you wanted this, from a code-review standpoint, it looks like you forgot to put & after auto . Therefore, I see no reason for using const auto . Use const auto& instead.
Use auto& when you want to modify elements in the range in non-generic code. The first part of the previous sentence should be clear as auto& will create references to the original elements in the range. To see why this code should not be used in generic code (e.g. inside templates), take a look at the following function template:
It will work. Well, most of the time. Until someone tries to use it on the dreaded std::vector<bool> . Then, the example will fail to compile because dereferencing an iterator of std::vector<bool> yields a temporary proxy object, which cannot bind to an lvalue reference ( auto& ). As we will see shortly, the solution is to use “one more & ” when writing generic code.
const auto&
Use const auto& when you want read-only access to elements in the range, even in generic code. This is the number one choice for iterating over a range when all you want to is read its elements. No copies are made and the compiler can verify that you indeed do not modify the elements.
Nevertheless, keep in mind that even though you will not be able to modify the elements in the range directly, you may still be able to modify them indirectly. For example, when the elements in the range are smart pointers:
In such situations, you have to pay close attention to what you are doing because the compiler will not help you, even if you write const auto& .
Use auto&& when you want to modify elements in the range in generic code. To elaborate, auto&& is a forwarding reference, also known as a universal reference. It behaves as follows:
- When initialized with an lvalue, it creates an lvalue reference.
- When initialized with an rvalue, it creates an rvalue reference.
A detailed explanation of forwarding references is outside of scope of the present post. For more details, see this article by Scott Meyers. Anyway, the use of auto&& allows us to write generic loops that can also modify elements of ranges yielding proxy objects, such as our friend (or foe?) std::vector<bool> :
Now, you may wonder: if auto&& works even in generic code, why should I ever use auto& ? As Howard Hinnant puts it, liberate use of auto&& results in so-called confuscated code: code that unnecessarily confuses people. My advice is to use auto& in non-generic code and auto&& only in generic code.
By the way, there was a proposal for C++1z to allow writing just for (x : range) , which would be translated into for (auto&& x : range) . Such range-based for loops were called terse. However, this proposal was removed from consideration and will not be part of C++.
const auto&&
This variant will bind only to rvalues, which you will not be able to modify or move because of the const . This makes it less than useless. Hence, there is no reason for choosing this variant over const auto& .
decltype(auto)
C++14 introduced decltype(auto) . It means: apply automatic type deduction, but use decltype rules. Whereas auto strips down top-level cv qualifiers and references, decltype preserves them.
As is stated in this C++ FAQ, decltype(auto) is primarily useful for deducing the return type of forwarding functions and similar wrappers. However, it is not intended to be a widely used feature beyond that. And indeed, there seems to be no reason for using it in range-based for loops.
Summary
- Use auto when you want to work with a copy of elements in the range.
- Use auto& when you want to modify elements in the range in non-generic code.
- Use auto&& when you want to modify elements in the range in generic code.
- Use const auto& when you want read-only access to elements in the range (even in generic code).
Other variants are generally less useful.
Further Information
Check out the following sources for more information regarding automatic type deduction and range-based for loops:
Also, if you are aware of any use cases when you would use one of the generally less useful variants, be sure to leave a comment.
Discussion
Apart from comments below, you can also discuss this post at r/cpp and Hacker News.
How C++ range-based for loop works
The range-based for loop changed in C++17 to allow the begin and end expressions to be of different types. And in C++20, an init-statement is introduced for initializing the variables in the loop-scope.
Tutorial | Sep 29, 2019 | nextptr 

Overview
The range-based for loop (or range-for in short), along with auto , is one of the most significant features added in the C++11 standard. These are some of the typical usages of range-based for loop:
This article, inspired by the cppreference page, explains the internal functioning of the range-for loop. We will start by outlining how C++11/C++14 range-for works, and then briefly describe the changes made to it in C++17 and C++20 in later sections.
Range-for in C++11/C++14
The range-based for loop has the following format:
In C++11/C++14, the above format results in a code similar to the following:
These are the focal points regarding the above implementation:
The Universal Reference:
The range is a universal reference (because it is declared as auto&& ). A universal reference, also known as a forwarding reference, can bind to either an lvalue or an rvalue expression. This suggests the range_expression can be anything including but not limited to — a variable, a const reference, or a function call that returns a temporary.
The Nesting Block:
The entire implementation is nested within a block (<>) statement. If the range_expression returns a temporary, the temporary’s lifetime is extended until the end of the loop by the enclosing block.
Types of beginExpr and endExpr:
The beginExpr and the endExpr are of the same type, and they are resolved depending upon the range_expression as follows:
If the range_expression is an array of N elements, the beginExpr is range and the endExpr is range+N.
If the range_expression is a class with members begin and end, the beginExpr is range.begin() and endExpr is range.end(). All the STL containers (e.g., std::vector and std::map ) have begin and end methods that return the iterators. Note that, if the begin and end members are not functions returning an iterator (or a pointer), this results in a compilation error.
If none of the above, the beginExpr is begin(range) and endExpr is end(range). Note that here, the begin and end are unqualified function names (e.g., begin instead of std::begin), and they are resolved using Argument Dependent Lookup (ADL). In a nutshell, ADL means, the compiler performs the lookup for an unqualified function name in its arguments’ namespaces also.
A Custom range-for Iterable
Let’s take an example of a custom range-for iterable type to get everything together. Consider a minimal null-terminated custom string class, FixedString, that can only store a fixed number of chars . The FixedString class also has an inner type Iterator and — begin() and end() — methods so it can be used in a range-for loop:
Although a FixedString object can be assigned a new value, its elements cannot be modified. This helps keep things straightforward for our purpose here as we don’t have to worry about defining the non- const begin and end methods. Note that instead of member functions begin/end, we could define free begin() and end() function templates in the same namespace to make FixedString iterable in the range-for loop:
We have chosen the member functions way because that helps us explain the C++17 changes in the next section. The FixedString can be used in a range-based loop as follows:
The range-based for loop has gone over some changes since C++11/C++14. The first change was made in C++17 to allow a range_expression’s end to be of a different type than its begin. The second and most recent change, which is from the C++20 standard, adds an optional init-statement for initializing the variables in the loop-scope. We talk about these changes in the next two sections.
The C++17 Version
Until C++14, the beginExpr and endExpr had to be of the same type, hence constraining the end-ness of the range_expression. Observing the range-for structure shows that the endExpr only needs to be equality comparable to beginExpr. The C++17 standard lifts this restriction on endExpr to be of the same type as beginExpr. Thus, since C++17, the endExpr can also be a sentinel integer value( e.g., a null byte) or even a predicate. The C++17 composition of the range-for loop is:
So how can we take advantage of this evolution for FixedString? We can modify the FixedString to have an end() method that returns a sentinel null char ( \0 ), instead of an end() method that returns an iterator. The FixedString::Iterator can be made comparable to a sentinel char value instead of its own type:
Please check out «An Iterable’s End May Have a Different Type Than Its Begin» for more details on this C++17 change.
The C++20 Version
The range-for loop since C++20 has the following format:
To understand the motivation behind adding init-statement, let’s consider a range_expression that returns a temporary:
That loop works fine because the lifetime of the temporary is extended until the end of the loop. However, if the range_expression is changed to have a temporary within it, the results are undefined:
A few more examples of a temporary within a range_expression that can be very tough to spot:
All the above cases can be resolved by introducing a variable before the loop to remove the intermediate temporary, e.g.:
Or, we can use the C++20’s init-statement as an alternative to create a variable in the loop-scope:
Clearly, the C++20 init-statement offers an elegant way to initialize a local scope variable for a range_expression.
Различия auto и auto&& внутри диапазонного for цикла
![]()
то данная 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
Что это означает? Это означает, что следующие два объявления будут эквивалентными
Ниже приведена демонстрационная программа
даст ошибку для auto& , т.к. vector<bool> использует вспомогательный ссылочный класс, rvalue-объект которого не может быть связан с неконстанной ссылкой. Из-за прокси-класса возникает ещё одна интересная ситуация, что модификация i в циклах auto и auto&& будут вести себя одинаково, т.е. изменять значение, хранимое в контейнере.
Если вместо std::vector<bool> использовать std::vector<int> , то auto позволит менять только локальную переменную, а auto&& (как и auto& ) уже само значение в контейнере.