auto in C++: Understand the details

Share the Article

Auto Keyword

The auto in C++ is a new keyword which helps to define any variable or function. However, this is always used with an initializer value or variable. At first, the compiler takes the datatype of the initializer on RHS and then deduces the same type for the variable on LHS.

The following example uses auto to define variables.

#include <iostream> //main header using namespace std; //namespace int main() { auto aa = 0; //int auto ab = aa; //int const auto ac = aa; //const int auto* ad = &aa; //Pointer to int cout << typeid(decltype(aa)).name() << endl; cout << typeid(decltype(ab)).name() << endl; cout << typeid(decltype(ac)).name() << endl; cout << typeid(decltype(ad)).name() << endl; return 0; }

The output is:

i i i Pi

Auto in c++ program is similar to template type deduction in most cases

The auto keyword work in the same way as template works. The auto plays the role of template parameter “T” (as shown below).

#include <iostream> //main header using namespace std; //namespace template<typename T> void funda(T tx) { cout << typeid(decltype(tx)).name() << endl; } int main() { auto aa = 0; auto ab = aa; const auto ac = aa; auto* ad = &aa; //No template initialization with explicit types funda(aa); funda(ab); funda(ac); funda(ad); return 0; }

In the the above program, the template parameter T is deduced by parameters of function calls. For instance, the function “funda” is called with three integer values and one pointer-to-int value.

Please note: There is no explicit instantiation of function template “funda”. The type T is deduced by values which are passed.

This will produce same output as we saw above with auto.

i i i Pi

Specifying auto in C++ like a universal reference

When the program uses && with auto as datatype, then variable becomes like a universal reference. We can initialize such universal referencewith either an L-value specifier or an R-value.

int ix = 7; auto&& aa = 0; //universal reference deduced to R-value type auto&& ab = ix; //universal reference deduced to L-value type

The following example, verifies that indeed, a is finally an R-value of type int and b is an L-value.

#include <iostream> //main header using namespace std; //namespace int main() { int ix; auto&& aa = 0; auto&& ab = ix; cout << typeid(decltype(aa)).name() << endl; cout << typeid(decltype(ab)).name() << endl; if(std::is_reference<decltype(aa)>() ) if(std::is_lvalue_reference<decltype(aa)>() ) cout << "L-Vaue" << endl; else cout << "R-Vaue" << endl; if(std::is_reference<decltype(ab)>() ) if(std::is_lvalue_reference<decltype(ab)>() ) cout << "L-Vaue" << endl; else cout << "R-Vaue" << endl; return 0; }

The output is:

i i R-Vaue L-Vaue

Auto&& is similar to universal reference

The following example rewrites the same logic with universal references. As per the design, the function “funda” is a template function and its parameter T is a universal reference. Nevertheless, the program do not explicitly initialize the function template “funda”. Instead, the compiler uses type of parameter value to deduce the type of template parameter. Finally, the parameter T becomes either an L-value or an R-value reference.

This example is exactly same as the previous one.

#include <iostream> //main header using namespace std; //namespace template<typename T> void funda(T&& tx) //tx is universal ref { cout << typeid(decltype(tx)).name() << endl; if(std::is_reference<decltype(tx)>() ) if(std::is_lvalue_reference<decltype(tx)>() ) cout << "L-Value" << endl; else cout << "R-Value" << endl; } int main() { int ix; funda(0); funda(ix); return 0; }

The output is similar as it was in case of auto&&.

i R-Value i L-Value

Specifier is of initializer list type

When the specifier contains curly braces and comma-separated values, then auto type deduces to become initializer_list type. This happens due to special type-deduction rule for auto. We will see in next section that same is not true for Template type deduction.

#include <iostream> //main header using namespace std; //namespace int main() { auto aa = {3,4,5}; cout << typeid(decltype(aa)).name() << endl; return 0; }

Output:

St16initializer_listIiE

However, the comma-separated values must be of the same type. Otherwise, the compiler shall generate an error.

auto aa = {3, 8.0}; //First is int, second is float

The error is:

compiler error due to inconsistent initializer_list

initializer_list passed to template function

Although auto keyword deduces to type intializer_list as shown in above example. However, same is not true for template parameters. Actually, there are two kinds of type deductions happening here. First is what kind of list is passed to deduce T1 and second is type of the elements in this list (T2).

Example:

T1 = initializer_list<T2> //Both T1 & T2 to be deduced
#include <iostream> //main header using namespace std; //namespace template<typename T> void funda(T tx) { } int main() { funda({3, 4}); return 0; }

The compiler shall generate following error:

error due to passing initializer_list to template function

For solving this problem, the template parameter should be written as “std::initializer_list<T>”. Now, compiler has to deduce only one type T. The following program now works.

#include <iostream> //main header using namespace std; //namespace template<typename T> void funda(std::initializer_list<T> x) { } int main() { funda({3, 4}); return 0; }

Limitation of auto: Function returning an initializer_list

When the function return type is “auto” and it returns an initializer_list, then auto keyword again cannot deduce types. This is an exception case, here, the type deduction rule for auto works just like templates. Therefore, it throws error just like templates.

#include <iostream> //main header using namespace std; //namespace auto funda() { return {1, 2, 3}; } int main() { return 0; }

The error is:

compiler error due to returning initializer_list

Main Funda: The type deduction rule for auto is similar to template type deduction, but there are scenarios where they can behave differently

Related Topics:

What are dependent scope type in templates?
What is an implicit interface? 
Calling member functions in template base class
What is template meta programming (TMP)
How delete keyword can be used to filter polymorphism
Concept of Inline Functions
What is reference collapsing?
How std::forward( ) works?
How std::move() function works?
Smart Pointers: shared_ptr <T> 
Smart Pointers: unique_ptr<T>
What is move constructor ?
Understanding Constant Variables

Share the Article

Leave a Reply

Your email address will not be published. Required fields are marked *