Jump to content

  • Log In with Google      Sign In   
  • Create Account

FREE SOFTWARE GIVEAWAY

We have 4 x Pro Licences (valued at $59 each) for 2d modular animation software Spriter to give away in this Thursday's GDNet Direct email newsletter.


Read more in this forum topic or make sure you're signed up (from the right-hand sidebar on the homepage) and read Thursday's newsletter to get in the running!






The New C++, Part 1 - auto

Posted by Washu, 29 February 2012 · 4,198 views

In this short series of entries I'm going to cover some of the new things that have been introduced into the C++ programming language with the latest standard. I will not be covering everything, as much of the new functionality has yet to be adopted or implemented in existing compilers, or are things that you probably shouldn't be playing with unless you have a lot of experience with the language, such as variadic templates. This first entry is going to focus on one of the more useful things added to the new C++ standard, auto. It will also give you a preview of the new function definition syntax, which will come in handy in future episodes.

How We Got Here

It has been nearly 14 years since C++ was first standardized. That makes the C++ standard pretty young and at the same time pretty old. Especially when you consider that, from a language perspective, the core standard has not changed any in those 14 years.
There have been a couple of updates to the standard through those 14 years, but they were all optional updates. If you wished to implement a compliant C++ compiler, you needed to only implement the standard as described in the original document. Unfortunately, that’s not as easy as it sounds. The C++ language is a language of corner cases, and it is actually not hard to see why when you grasp one of the underlying thought concepts of the C++ language committee: Reserved words are bad.

When you look through the C++ language you find that there are approximately 73 reserved words, and if you look closer you will see that many of those reserved words get reused in various contexts throughout the standard. For a quick and simple example, the “class” keyword changes its behavior depending on if it’s used in a template declaration or to define/declare a type. That, of course, is not the only reason the language is hard to parse, but it is part of the reason.

There have been several updates to the standard that have clarified various parts of it, and the C++ Technical Report 1 (TR1) added a whole bunch of new libraries to the C++ standard library, including tuple types, array, various hashed containers, regular expressions, a whole slew of mathematical functions, and a whole bunch of new random number facilities. Of course, since these were library extensions none of them were mandatory, but some compilers attempted to implement most or all of TR1.

Working with an Example

For the purposes of this entry we’ll be working with some basic code, and implementing various bits and pieces using the new C++11 standard. I’ll be using the Visual Studio 2010 compiler for this, although GCC should also work. Clang will work with most of the examples, however they do not yet have lambda support in, so the final pieces on lambdas will not compile in clang. Yet.

#include <iostream>
#include <vector>
#include <functional>

template<class Sequence1, class Sequence2, class MatchBinaryFunctor>
std::pair<typename Sequence1::const_iterator, typename Sequence2::const_iterator> find_first_pair(Sequence1 const& seq1, Sequence2 const& seq2, MatchBinaryFunctor match)
{
	for(typename Sequence1::const_iterator itor1 = seq1.begin(); itor1 != seq1.end(); ++itor1) {
		for(typename Sequence2::const_iterator itor2 = seq2.begin(); itor2 != seq2.end(); ++itor2) {
			if(match(*itor1, *itor2)) {
				return std::make_pair(itor1, itor2);
			}
		}
	}

	return std::make_pair(seq1.end(), seq2.end());
}

bool is_equal_and_odd(int lhs, int rhs) {
	return lhs == rhs && lhs % 2 != 0;
}

int main() {
	int v1ints[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
	int v2ints[] = {13, 4, 5, 7};

	std::vector<int> v1(v1ints, v1ints + 10);
	std::vector<int> v2(v2ints, v2ints + 4);

	std::pair<std::vector<int>::const_iterator, std::vector<int>::const_iterator> p = find_first_pair(v1, v2, is_equal_and_odd);

	std::cout<<*p.first<<":"<<*p.second<<std::endl;
}
This is a fairly simple piece of code that really does only one thing: It searches two containers passed to it for the first entries that the match functor returns true on, and then returns a pair of iterators to those two elements. Otherwise it returns iterators to the end of each container.
If you run this piece of code you get the output “5:5”, as that’s clearly the first entries that match our function is_equal_and_odd.

Auto – The Formerly Most Useless Keyword Ever

Let’s talk a bit about auto. Auto, prior to C++11, was one of those keywords you never saw, for good reason to. A variable declared auto had “automatic storage duration.” Interestingly enough though, variables NOT declared static or extern ALSO had automatic storage duration. Which meant that the difference between “auto int a;” and “int a;” was literally nothing. So why did it exist? Because it was in C, and C is included in the C++ standard.

When it came time for work to start on C++11 one of the things desired was to add static type inference. Type inference is the ability for the TYPE of a variable to be figured out based on its context. Static type inference is static, thus the type of the variable doesn’t change and it can be computed at compile time. Thus auto was repurposed to be used in this manner. Of course, when you now declare an auto variable you no longer provide a type, but you must provide an initializer expression whose type is compile time knowable. Thus you can now say auto a = 0; whose type will be integer (as that is the type of the literal 0). As such auto str = std::string("Hello world") clearly is declaring a variable of type std::string.

Unfortunately, you cannot use auto everywhere you might like to. Function parameters cannot be auto, templates are for that. Furthermore, when we get into them, lambda parameters also cannot be declared auto. That last one there is perhaps one my biggest issues with the new standard.

With that bit of knowledge under our belt and the above code to work with we can clearly see several areas where just some small changes using auto can make the code significantly simpler and easier to read. If we replace the iterators in the for loops, and the declaration of p in the main method with auto we get the following piece of code, which I’m sure you’ll agree is much simpler:
#include <iostream>
#include <vector>
#include <functional>

template<class Sequence1, class Sequence2, class MatchBinaryFunctor>
std::pair<typename Sequence1::const_iterator, typename Sequence2::const_iterator> find_first_pair(Sequence1 const& seq1, Sequence2 const& seq2, MatchBinaryFunctor match)
{
	for(auto itor1 = seq1.begin(); itor1 != seq1.end(); ++itor1) {
		for(auto itor2 = seq2.begin(); itor2 != seq2.end(); ++itor2) {
			if(match(*itor1, *itor2)) {
				return std::make_pair(itor1, itor2);
			}
		}
	}

	return std::make_pair(seq1.end(), seq2.end());
}

bool is_equal_and_odd(int lhs, int rhs) {
	return lhs == rhs && lhs % 2 != 0;
}

int main() {
	int v1ints[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
	int v2ints[] = {13, 4, 5, 7};

	std::vector<int> v1(v1ints, v1ints + 10);
	std::vector<int> v2(v2ints, v2ints + 4);

	auto p = find_first_pair(v1, v2, is_equal_and_odd);

	std::cout<<*p.first<<":"<<*p.second<<std::endl;
}
It is important to note that auto does not automatically handle referencing. If you desire a reference auto type, you must specify that you desire a reference. As a trivial example:
auto itor = seq1.begin(); // by value.
auto& itor = seq1.begin(); // by reference.
A Newly Functional Way

Of course, reading this over you can see that the code is still pretty hard to read. I mean, where does the return type end and the function name start? Quite far in, makes it not so easy for the brain to parse, eh? One of the new tidbits that came with C++11 was a little thing called decltype, which we’ll get into later. As part of decltype though came something else quite useful as well: A new style of function decleration. For the first time you can now declare the return type of the function AFTER the parameter portion of the function. Of course, you must still provide something to prefix the function with… hello auto. The syntax is a bit wonky though, I must warn you Posted Image.
template<class Sequence1, class Sequence2, class MatchBinaryFunctor>
auto find_first_pair(Sequence1 const& seq1, Sequence2 const& seq2, MatchBinaryFunctor match) -> std::pair<typename Sequence1::const_iterator, typename Sequence2::const_iterator>
{
	for(auto itor1 = seq1.begin(); itor1 != seq1.end(); ++itor1) {
		for(auto itor2 = seq2.begin(); itor2 != seq2.end(); ++itor2) {
			if(match(*itor1, *itor2)) {
				return std::make_pair(itor1, itor2);
			}
		}
	}

	return std::make_pair(seq1.end(), seq2.end());
}
This is, surprisingly, a lot easier to read. You can immediately find the return type (everything after the arrow), and you can find the function decleration as well. What would really make this a lot nicer is if we could somehow get rid of that horrible template mess in the return type. Ahh well, more on that later.




Thanks for this very informative post.

I would like to point out the video coverage of the "Going Native 2012" Conference which unsurprisingly concentrated on the new C++11 standard. Featuring Bjarne Stroustrup, Herb Sutter, Stephan T. Lavavej and Andrei Alexandrescu among others as speakers.

http://channel9.msdn.com/Events/GoingNative/GoingNative-2012
Yes, I've seen those presentations. I've been following the development of the new standard for a number of years now, sometimes with glee, and sometimes with trepidation.

My purpose here is not to necessarily be a definitive guide to all of the new features of C++11, but more as an introduction and application of its more visible components that are immediately available.

Unfortunately, a lot of what is covered by those presentations isn't available even in modern compilers, or has very buggy support indeed. Examples include the standard threading library. Boost threads do the trick, but are in the wrong namespace. Other examples include the fences, barriers, and variadic templates.
PARTNERS