C++11 Tutorial: Let Your Compiler Detect the Types of Your Objects Automatically

Imagine that your compiler could guess the type of the variables you declare as if by magic. In C++11, it’s possible — well, almost. The new auto and decltype facilities detect the type of an object automatically, thereby paving the way for cleaner and more intuitive function declaration syntax, while ridding you of unnecessary verbiage and keystrokes. Find out how auto and decltype simplify the design of generic code, improve code readability, and reduce maintenance overhead.

C

 

Fact #1: Most declarations of C++ variables include an explicit initializer. Fact #2: The majority of those initializers encode a unique datatype. C++11 took advantage of these facts by introducing two new closely related keywords: auto and decltype. auto lets you declare objects without specifying their types explicitly, while decltype captures the type of an object.

 

Together, auto and decltype make the design of generic code simpler and more robust while reducing the number of unnecessary keystrokes. Let’s look closely at these new C++11 silver bullets.

 

auto Declarations

As stated above, most of the declarations in a C++ program have an explicit initializer (on a related note, you may find this article about class member initializers useful). Consider the following examples:

int x=0;

const double PI=3.14;

string curiosity_greeting (“hello universe”);

bool dirty=true;

char *p=new char [1024];

long long distance=10000000000LL;

int (*pf)() = func;

Each of the seven initializers above is associated with a unique datatype. Let’s examine some of them:

  • The literal 0 is int.
  • Decimal digits with a decimal point (e.g. 3.14) are taken to be double.
  • The initializer new char [] implies char*.
  • An integral literal value with the affix LL (or ll) is long long.

The compiler can extract this information and automatically assign types to the objects you’re declaring. For that purpose, C++11 introduced a new syntactic mechanism. A declaration whose type is omitted begins with the new keyword auto. An auto declaration must include an initializer with a distinct type (an empty pair of parentheses won’t do). The rest is relegated to the compiler. How convenient!  

Note: C++ actually has two auto keywords: the old auto and the new auto. The old auto dates back to the late 1960s, serving as a storage class in declarations of variables with automatic storage. Since automatic storage is always implied from the context in C++, the old auto was seldom used, which is why most programmers didn’t even know it existed in C++ until recently.C++11 removed the old auto (though it still exists in C) in favor of the new auto, which is used in auto declarations. Some implementations may require that you turn on C++11 computability mode to support the new auto. In this article, I refer exclusively to the new auto.

 

Here are the previous seven declarations transformed into auto declarations:

//C++11

auto x=0;

const auto PI=3.14;

auto curiosity_greeting= string (“hello universe”);

auto dirty=true;

auto p=new char [1024];

auto distance=10000000000LL;

auto pf = func;

In the case of class objects such as std::string, you need to include the name of the class in the initializer to disambiguate the code. Otherwise, the quoted literal string “hello universe” would be construed as const char[15].

Const, Arrays, and Reference Variables

If you want to declare cv-qualified objects, you have to provide the const and volatile qualifiers in the auto declaration itself. That’s because the compiler can’t tell whether the initializer 3.14 is plain double, const double, volatile double, or const volatile double:

volatile auto i=5; //volatile int

auto volatile const flag=true; //const volatile bool

auto const y=’a’; //const char

To declare an auto array, enclose the initializers in braces. The compiler counts the number of initializers enclosed and uses that information to determine the size (and dimensions) of the array:

auto iarr={0,1,2,3}; //int [4]

auto arrarr={{0,1},{0,1}}; //int[2][2]

Tip: Certain implementations insist that you #include the new standard header <initializer_list> when you declare an auto array. Technically, the C++11 standard requires that an implementation #include <initializer_list> implicitly whenever that header is needed, so you’re not supposed to write any #include <initializer_list> directives, ever. However, not all implementations are fully compliant in this respect. Therefore, if you encounter compiler errors in array auto declarations, try to #include <initializer_list> manually.

Declaring an auto array of pointers to functions (and pointers to member functions) follows the same principles:

int func1();

int func2();

auto pf_arr={func1, func2};//array  int (*[2])()

 

int (A::*pmf) (int)=&A::f; //pointer to member

auto pmf_arr={pmf,pmf}; //array int (A::*[2]) (int)

Reference variables are a slightly different case. Unlike with pointers, when the initializer is a reference type, auto still defaults to value semantics:

int& f();

auto x=f(); //x is int, not int&

To declare a reference variable using auto, add the & to the declaration explicitly:

auto& xref=f(); //xref is int&

Technically, you can declare multiple entities in a single auto expression:

auto x=5, d=0.54, flag=false, arr={1,2,3}, pf=func;

Operator decltype

The new operator decltype captures the type of an expression. You can use decltype to store the type of an object, expression, or literal value. In this sense, decltype is the complementary operation of auto – whereas auto instructs the compiler to fill-in the omitted type, decltype “uncovers” the type of an object:

auto x=0;  //int

decltype (x)  z=x; //same as auto z=x;

typedef decltype (x) XTYPE; //XTYPE is a synonym for int

XTYPE y=5;

How does it work? The expression decltype(x) extracts the type of x, which is int. That type is assigned to the new typedef name XTYPE. XTYPE is then used in the declaration of a new variable, y, which has the same type as x.

Let’s look at a more realistic example in which decltype extracts the iterator type from a generic expression. Suppose you want to capture the iterator type returned from a vector<T>::begin() call. You can do it like this:

vector<Foo> vf;

typedef decltype(vf.begin()) ITER;

for (ITER it=vf.begin(); it<vf.end(); it++)

  cout<<*it<<endl;

Notice that decltype takes an expression as its argument. Therefore, you can apply decltype not just to objects and function calls, but also to arithmetic expressions, literal values, and array objects:

typedef decltype(12) INT;  //a literal value

float f;

typedef decltype(f) FLOAT; //a variable

typedef decltype (std::vector<int>()) VI; //a temporary

typedef decltype(4.9*0.357) DOUBLE; //a math expression

auto arr={2,4,8};

typedef decltype(arr) int_array;//an array

int_array table;

table[0]=-10;

C++11 Style Function Declarations

C++11 introduced a new notation for declaring a function using auto and decltype. Recall that a traditional function declaration looks like this:

return_type name(params,...); //old style function declaration

In the new function declaration notation, the return type appears after the closing parenthesis of the parameter list and is preceded by a -> sign. For example:

auto newfunc(bool b)->decltype(b)//C++11 style function declaration

{ return b;}

newfunc() takes a parameter of type bool and returns bool. This style is useful in template functions where the return type depends on the template-id (the actual template instance). Thus, template functions can define a generic return type using the new notation and decltype. The actual return type is calculated automatically, depending on the type of the decltype expression:

//return type is vector<T>::iterator 
template <class T> auto get_end (vector<T>& v) ->decltype(v.end());

{return v.end(); }

vector<Foo> vf;

get_end(vf); //returns vector<Foo>::iterator

const vector<Foo> cvf;

get_end(cvf); //returns vector<Foo>::const_iterator

The new function declaration notation overcomes parsing ambiguity problems that were associated with the traditional function declaration notation. Additionally, this notation is easier to maintain because the actual return type isn’t hardcoded in the source file. As a bonus, it is said to be more readable.

In Conclusion

auto and decltype fill in a gap that, until recently, forced programmers to use hacks and non-standard extensions, such as the GCC-specific typeof() operator. auto doesn’t just eliminate unnecessary keystrokes. Rather, it also simplifies complex declarations and improves the design of generic code. decltype complements auto by letting you capture the type of complex expressions, literal values, and function calls without spelling out (or even knowing) their type. Together with the new function declaration notation, auto and decltype make a C++11 programmer’s job a tad easier.

 

Danny Kalev is a certified system analyst and software engineer specializing in C++. Kalev has written several C++ textbooks and contributes C++ content regularly on various software developers’ sites. He was a member of the C++ standards committee and has a master’s degree in general linguistics.

See also:



peercodereview-best-kept-secrets-wp-cta




subscribe-to-our-blog