C++ 11 Auto: How to use and avoid abuse
My first encounter with the C++ 11 Auto keyword was in less than favorable circumstances. I had just moved to a new team, ventured into an area of hardcore C++ network and protocol development for Xbox, and struggling to understand some complicated game engine infrastructure like Halo and Gears of War. To top it all, I had not written C++ in over six years.
It was late friday afternoon and I encounter something like :
auto a = ConjureMagic();
SetMagic(a);
The immediate question that cropped up in my mind was “what the heck is ‘a’ ?!? “ . The IDE helped a little bit because I could see the types if I hovered over the variable name in Visual Studio. However, hovering over the names every time I tried to find the type was very annoying. It disrupted my thought process of just reading the code and understanding the flow. But to my dismay, the code base was littered with it.
Fast forward 16 months and I now realize that my frustration with C++ 11 Auto keyword stemmed from the way it was used, and not the nature of the keyword itself. In fact, I’ve grown to be an advocate of using “auto” over the last year. Before I get into the reasons for being an “auto” convert , here’s a quick recap of what the “auto” keyword is.
Auto keyword simply tells the compiler to deduce the type of a declared variable from its initialization expression. It analogous to the “var” keyword in C# . Here’s are four ways it has made my C++ development life easier:
#1 C++ 11 Auto makes defining complex or templatized data types a breeze
Auto cuts down on unnecessary typing of complex data types on the left hand side of assignment operator. For example, consider the two code snippets below used to initialize the task scheduler for C++ concurrency runtime.
The first one uses the pre C++ 11 way of initializing variables (minus the fact it uses C++ 11 smart pointer):
std::shared_ptr<::pplx::default_scheduler_t> s_ambientScheduler = std::make_shared<::pplx::default_scheduler_t>();
Now consider the alternative with auto:
auto s_ambientScheduler = std::make_shared<::pplx::default_scheduler_t>();
Isn’t the second version much easier to read ? Here we’re already seeing what type s_ambientScheduler is from it’s initialization expression on the right – so no need to add verbosity to the code by mentioning the explicit type on the left. This is pretty much in line with DRY ( don't repeat yourself ) principle of software development.
#2 C++ 11 Auto makes STL iterator loops easier to write and comprehend
This is a big one. Prior to C++ 11, we needed to use fully qualified iterator types for looping through STL containers. The problem gets really complicated as we start using nested STL containers. For example, consider a nested STL map. It’s used to store the name of a student and the various grades he has received in different subjects.
std::map<std::wstring, std::map<std::wstring, int>> StudentGrades;
StudentGrades[L"Deb"][L"Physics"] = 96;
StudentGrades[L"Deb"][L"Chemistry"] = 92;
StudentGrades[L"Deb"][L"Math"] = 82;
StudentGrades[L"Vik"][L"Physics"] = 92;
StudentGrades[L"Vik"][L"Chemistry"] = 88;
StudentGrades[L"Vik"][L"Math"] = 91;
If we need to print out the grades, this is what the code would have looked like prior to C++ 11 (i.e. without using the auto keyword):
for (std::map<std::wstring, std::map<std::wstring, int>>::iterator outerMap_Iter = StudentGrades.begin(); outerMap_Iter != StudentGrades.end(); ++outerMap_Iter)
{
//Print out the student name
std::wcout << outerMap_Iter->first << std::endl;
for (std::map<std::wstring, int>::iterator innerMap_Iter = outerMap_Iter->second.begin(); innerMap_Iter != outerMap_Iter->second.end(); ++innerMap_Iter)
{
//Print the grades here
std::wcout << innerMap_Iter->first << " : " << innerMap_Iter->second << std::endl;
}
std::wcout << std::endl;
}
Does that make your head hurt? yeah – mine too !! The damn thing does not even fit on my laptop screen without showing the horizontal scroll-bars. But we had no alternatives before. Now we do – consider the consider the C++ 11 alternative with auto:
for (auto outerMap_Iter = StudentGrades.begin(); outerMap_Iter != StudentGrades.end(); ++outerMap_Iter)
{
//Print out the student name
std::wcout << outerMap_Iter->first << std::endl;
for (auto innerMap_Iter = outerMap_Iter->second.begin(); innerMap_Iter != outerMap_Iter->second.end(); ++innerMap_Iter)
{
//Print the grades here
std::wcout << innerMap_Iter->first << " : " << innerMap_Iter->second << std::endl;
}
std::wcout << std::endl;
}
Here, instead of spelling out the iterator type , we let the compiler auto deduce it from the instantiation. And it almost fits on one screen !
If you’re already hooked, it gets even better when combined with a range ranged for loop in C++ 11:
for (auto const &outer_iter : StudentGrades)
{
std::wcout << outer_iter.first << std::endl;
for (auto const &inner_iter : outer_iter.second)
{
std::wcout << inner_iter.first << " : " << inner_iter.second << std::endl;
}
}
Now we’re talking ! Contrast this with our first implementation – just a glance at the two lines below shows the big picture:
Implementation #1 :
for (std::map<std::wstring, std::map<std::wstring, int>>::iterator outerMap_Iter = StudentGrades.begin(); outerMap_Iter != StudentGrades.end(); ++outerMap_Iter)
Implementation# 3:
for (auto const &outer_iter : StudentGrades)
Yes, implementation # 3 just saved you 111 keystrokes if you're writing this code and some scrolling and headache if you're reading this code !!!
#3 C++ 11 Auto comes in handy while storing lambda closures
C++ 11 lets you store lambda expressions in named variables in the same manner you name ordinary variables and functions. This enables you to use the lambda expression multiple times in different places without having to copy the code all the time. The auto keyword will take care to define func as a pointer to the lambda expression.
auto func_multiply = [](int a, int b) -> int { return a * b; };
This auto declaration defines a closure type named factorial that you can call later instead of typing the entire lambda expression(a closure type is in fact a compiler – generated function class) :
std::cout << func_multiply(2, 3) << std::endl;
At this point, you might ask what the alternative is ? Well, the alternative is to use a function object to store the lambda. Here’s an example:
std::function<int(int, int)> func_multiply2 = [](int a, int b) -> int { return a * b; };
std::cout << func_multiply2(2, 3) << std::endl;
See how ugly the left hand side looks ? I headache just graduated to a migranine 🙂 Jokes aside, using a function object instead of auto has two other ramifications – it can sometimes allocate heap memory to store the closure. This can lead to out-of-memory exceptions at certain times. Also, invoking a closure via std::function is slower than calling it via an auto declared object. For a more in depth discussion, you can check out item # 5 of Scott Meyer's "Effective Modern C++".
#4 C++ 11 Auto forces initialization of variables
auto x1; // does not compile
int x1; // ok for the compiler
Uninitialized variables in C++ are one of the worst sources of bugs in C++. We had a situation where our code was relying on an uninitialized variable as a multiplicative factor to determine the cache size on web front ends. When we deployed the solution to our staging/ test servers , it started causing random out of memory exceptions to the point where the front ends became unresponsive and had to be taken out of rotation. The problem was caused by the fact that the uninitialized variable sometimes held a very large value that was used to allocate server cache. To compound problems finding uninitialized variables, variable declared when running the program in a debugger are typically zeroed. This means your program may work fine every time when run in a debugger, but crash intermittently in release mode! So morale of the story – minimize the chances of getting into a situation where you might have uninitialized variables – using auto for your local variables helps with just that.
However, you need to be careful with C++ 11 Auto !
Okay, now that we've seen some of the ways auto can help us write consise and robust code, you might ask – "But Deb, what about your initial complain? ". Well, I still don't like two ways in which some folks use auto, namely:
#1 To Intercept the value of functions and then pass them as parameters to another function
auto a = ConjureMagic();
SetMagic(a);
I think in this situation we can do either of two things. Change the name of the function to something more descriptive ConjureMagicInteger(). Even better, just use the fully qualified type in this case.
auto a = ConjureMagic() ; // worst
auto a = ConjureMagicInteger() ; // better
int a = ConjureMagicInteger(); // best
#2 To capture the result of an asynchronous operation
auto asyncResult = std::async(&ConjureMagic);
auto v = asyncResult.get();
The async() operation returns a std::future object that can be queried for the result once the async operation has finished. The issue with the code above is that I have no idea what variable v is. Of course, I can use contextual evidence or use the Visual Studio intellisence to figure out the type – but it's just more convenient to be more specific about the future type. For example, we can rewrite the same piece of code as:
std::future asyncResult2 = std::async(&ConjureMagic);
int v = asyncResult2.get();
Final words on C++ 11 Auto
The main thing to remember while using auto is this : Use auto where ever you believe it enhances code readability and avoid it where ever it obscures the intent of the code. At the end of the day, you're writing the piece of code for the next person who picks up your module and not the compiler , right ? 🙂