3 Reasons You Should Use C++11

This article is part 2 of a 4 part series on the benefits of the C++11 revision of the C++ programming language:

  1. Introduction to C++11 Toolchains
  2. 3 reasons you should use c++11
  3. 4 More Reasons You Should Use C++11
  4. Some Final Thoughts on C++11

C++11 is the first update to the standard since 2003 and brings many significant features into the language. This article covers three of the most valuable features of C++11: variadic templates, type and template aliases, and type inference.

Variadic templates provide functionality to handle type packs, allowing you to create templates with a variable number of template parameters as well as true, type safe variadic functions. The new type alias syntax allows for better readability and templating. Type inference lets the language deduce types of expressions, thus reducing verbosity and removing the need to explicitly specify types in many places.

Variadic Templates

In my opinion variadic templates are the single most useful feature introduced in the new standard. They provide the ability to create type packs, allowing for extended template metaprogramming, but more significantly, sane variadic functions.

You might be asking, what’s wrong with classic style variadic functions? Well, C++ inherited them from C and they utilize the stdarg.h header to manipulate va_lists. The va_arg macro provides the ability to retrieve the next value in the variadic parameter pack. This approach has two main problems, a lack of type awareness and an inconvenient API.

Lack of Type Awareness

In the case of something like printf("%d\n", 5); you have to specify the type as part of the format string. This system almost completely erases static type checking, leaving a vague object whose type needs to be known beforehand. This is bug prone, and is generally inconvenient, especially in a language that allows extensive static metaprogramming. Additionally, optimizing such functions becomes more difficult.

Inconvenient API

Have you ever noticed how every variadic function in C provides a variant for the actual parameter pack as well as for va_list? This could all be solved if variadic functions were type safe and weren’t required to have a special API for handling parameter packs.

The variadic API is also poorly defined when it comes to va_list copies. This causes inconveniences across compilers, because every toolchain chooses a different internal representation of va_list, with wildly different semantics. This is partially addressed in C99 with the va_copy macro, which hides some of the awkward semantic differences, but is still far from ideal.

C++11 Variadic Templates To the Rescue!

C++11 variadic templates solve both problems. They don’t need an API, as they’re a language feature; and they’re completely type safe, since the function is a template and will expand at compile time into a non-variadic variant.

So, let’s declare a C++11 variadic function, shall we? Let’s define an example function that will call something over each argument.

// forward declare
template<typename F, typename T, typename ...A>
void for_each_arg_impl(F &callable, T &&v, A &&...args);

template<typename F>
void for_each_arg_impl(F &) {}

template<typename F, typename T, typename ...A>
void for_each_arg_impl(F &callable, T &&v, A &&...args) {
    callable(v);
    for_each_arg_impl(callable, std::forward<A>(args)...);
}

template<typename F, typename ...A>
void for_each_arg(F callable, A &&...args) {
    for_each_arg_impl(callable, std::forward<A>(args)...);
}

Unfortunately, it’s not possible to iterate over parameter packs. If you think about it, there really can’t be. We have a heterogeneous tuple of types, and the only way to handle this is through recursion. Additionally, a parameter pack can be empty so we need to declare a “sentinel” function for when nothing is passed (i.e. when the args pack is empty) to stop the traversal.

One final thing to note is that when passing argument packs like this, they can be annotated as references, const references, or rvalue references just like if they were a single argument. This will get applied to every type in the pack. In this case, we utilize perfect forwarding with rvalue references (will be described in the next article).

To retrieve the size of a pack, you can use sizeof...(A).

Variadic functions are not the only use for variadic templates. You can use them within your template metaprogramming algorithms and treat them just like standard template types. With the exception that it isn’t possible to actually use type packs directly (i.e. you can’t make an alias to them). For example, consider the creation of a template that allows the construction of a function pointer type with arbitrary args and return type. It’s easy:

template<typename R, typename ...A>
struct FuncPtr {
    using type = R (*)(A...);
};

New Type Aliases and Template Aliases

C allows you to create type aliases using the typedef keyword. It’s messy and allows multiple syntaxes. For example, these two are identical:

typedef int foo;
int typedef foo;

It gets worse for function pointers:

typedef return_type (*alias)(arg_types);

Additionally, it doesn’t allow templating. So if you want to create an alias for a structure that fills in some template types and leaves the others to the user, you’re out of luck. Fortunately, C++11 introduced a new syntax:

using foo = int;

Isn’t it cleaner? For function pointers, nothing changes:

using alias = return_type (*)(arg_types);

And you can template it as easily:

template<typename T, typename U>
struct Something { ... };

template<typename U>
using PartialSomething = Something<int, U>;

It also allows type traits to use syntax that is much nicer. Consider for example std::remove_cv. To get the type, the code will be quite ugly:

typename std::remove_cv<T>::type

Template aliases allow this to be turned into a much nicer syntax:

template<typename T>
using remove_cv_t = typename std::remove_cv<T>::type;

/* now it's used just like that */
remove_cv_t<T>

C++14 adds these aliases into the standard library. In C++11, they have to be created manually.

Type Inference

C++11 finally introduced type inference. However it’s only local type inference, so it can’t do complicated whole-program analysis. With that said, for the purposes of the language it’s good enough; global inference requires careful design from the start anyway and wouldn’t work very well.

The simplest kind is to use the auto keyword:

auto x = foo();

The type of x will be inferred automatically. This is not the only kind of type inference in the language. It now possible to use decltype() to infer types of expressions. For example:

static_assert(std::is_same<decltype(5 + 3), int>::value, "not the same");

You can use decltype() in any place a type would be expected. It also integrates neatly with expression SFINAE, which will be covered later in this series..

C++11 also allows trailing return type syntax. Using this, it’s possible to write functions like this:

auto foo(int a, int b) -> decltype(a + b) { return a + b; }

Replacing the auto would be insufficient, because the variables that are being manipulated don’t exist yet as they’re yet to be parsed. Speaking of which, in C++14, it’s possible to drop the decltype() entirely, as the language can just figure it out.

It isn’t always possible to access variables. Using std::declval(), it’s possible to declare placeholders of any type to be used in constant expressions; there is no runtime representation of declval results.

decltype(declval<const MyObject &>().foo(declval<int>()))

This will resolve to return type of a method foo(x) called on const reference to MyObject with an int parameter.

Onward to More C++11!

There are plenty of reasons to build software with C++11, these are just a handful of the most important. In particular, variadic templates are a very significant feature that developers can benefit from greatly by gaining true type safety for variadic functions as well as a cleaner API. Additionally, New type aliases help make code more readable, and type inference reduces verbosity, allowing for nicer more concise code.

But wait, there’s more! Part three of the series will cover four additional major features of C++11: lambda expressions, expression SFINAE, rvalue references and move semantics, and constexpr. Stay tuned!

Author: Daniel Kolesa

Daniel has been contributing to open source since 2007 and has been involved with the EFL and the OctaForge 3D game engine in addition to other projects.