auto

Remarks

The keyword auto is a typename that represents an automatically-deduced type.

It was already a reserved keyword in C++98, inherited from C. In old versions of C++, it could be used to explicitly state that a variable has automatic storage duration:

int main()
{
  auto int i = 5; // removing auto has no effect
}

That old meaning is now removed.

auto and Expression Templates

auto can also cause problems where expression templates come into play:

auto mult(int c) {
    return c * std::valarray<int>{1};
}

auto v = mult(3);
std::cout << v[0]; // some value that could be, but almost certainly is not, 3.

The reason is that operator* on valarray gives you a proxy object that refers to the valarray as a means of lazy evaluation. By using auto, you're creating a dangling reference. Instead of mult had returned a std::valarray<int>, then the code would definitely print 3.

auto and proxy objects

Sometimes auto may behave not quite as was expected by a programmer. It type deduces the expression, even when type deduction is not the right thing to do.

As an example, when proxy objects are used in the code:

std::vector<bool> flags{true, true, false};
auto flag = flags[0];
flags.push_back(true);

Here flag would be not bool, but std::vector<bool>::reference, since for bool specialization of template vector the operator [] returns a proxy object with conversion operator operator bool defined.

When flags.push_back(true) modifies the container, this pseudo-reference could end up dangling, referring to an element that no longer exists.

It also makes the next situation possible:

void foo(bool b);

std::vector<bool> getFlags();

auto flag = getFlags()[5];
foo(flag);

The vector is discarded immediately, so flag is a pseudo-reference to an element that has been discarded. The call to foo invokes undefined behavior.

In cases like this you can declare a variable with auto and initialize it by casting to the type you want to be deduced:

auto flag = static_cast<bool>(getFlags()[5]);

but at that point, simply replacing auto with bool makes more sense.

Another case where proxy objects can cause problems is expression templates. In that case, the templates are sometimes not designed to last beyond the current full-expression for efficiency sake, and using the proxy object on the next causes undefined behavior.

auto, const, and references

The auto keyword by itself represents a value type, similar to int or char. It can be modified with the const keyword and the & symbol to represent a const type or a reference type, respectively. These modifiers can be combined.

In this example, s is a value type (its type will be inferred as std::string), so each iteration of the for loop copies a string from the vector into s.

std::vector<std::string> strings = { "stuff", "things", "misc" };
for(auto s : strings) {
    std::cout << s << std::endl;
}

If the body of the loop modifies s (such as by calling s.append(" and stuff")), only this copy will be modified, not the original member of strings.

On the other hand, if s is declared with auto& it will be a reference type (inferred to be std::string&), so on each iteration of the loop it will be assigned a reference to a string in the vector:

for(auto& s : strings) {
    std::cout << s << std::endl;
}

In the body of this loop, modifications to s will directly affect the element of strings that it references.

Finally, if s is declared const auto&, it will be a const reference type, meaning that on each iteration of the loop it will be assigned a const reference to a string in the vector:

for(const auto& s : strings) {
    std::cout << s << std::endl;
}

Within the body of this loop, s cannot be modified (i.e. no non-const methods can be called on it).

When using auto with range-based for loops, it is generally good practice to use const auto& if the loop body will not modify the structure being looped over, since this avoids unnecessary copies.

Basic auto sample

The keyword auto provides the auto-deduction of type of a variable.

It is especially convenient when dealing with long type names:

std::map< std::string, std::shared_ptr< Widget > > table;
// C++98
std::map< std::string, std::shared_ptr< Widget > >::iterator i = table.find( "42" );
// C++11/14/17
auto j = table.find( "42" );

with range-based for loops:

vector<int> v = {0, 1, 2, 3, 4, 5};
for(auto n: v)
    std::cout << n << ' ';

with lambdas:

auto f = [](){ std::cout << "lambda\n"; };
f();        

to avoid the repetition of the type:

auto w = std::make_shared< Widget >();

to avoid surprising and unnecessary copies:

auto myMap = std::map<int,float>();
myMap.emplace(1,3.14);

std::pair<int,float> const& firstPair2 = *myMap.begin();  // copy!
auto const& firstPair = *myMap.begin();  // no copy!

The reason for the copy is that the returned type is actually std::pair<const int,float>!

Generic lambda (C++14)

C++14

C++14 allows to use auto in lambda argument

auto print = [](const auto& arg) { std::cout << arg << std::endl; };

print(42);
print("hello world");

That lambda is mostly equivalent to

struct lambda {
    template <typename T>
    auto operator ()(const T& arg) const {
        std::cout << arg << std::endl;
    }
};

and then

lambda print;

print(42);
print("hello world");

Trailing return type

auto is used in the syntax for trailing return type:

auto main() -> int {}

which is equivalent to

int main() {}

Mostly useful combined with decltype to use parameters instead of std::declval<T>:

template <typename T1, typename T2>
auto Add(const T1& lhs, const T2& rhs) -> decltype(lhs + rhs) { return lhs + rhs; }


2016-07-22
2016-10-17
C++ Pedia
Icon