01 Jan, 2013

Prologue

There was once a blog about a series of tales, memories gathered during a never-ending journey of C++ development, a recollection of advice helpful to those navigating these wild lands. The whispers went quieter, and for a while no more tales where heard... It’s once again an exciting time for C++, and with that the whispers awakened and got louder one more time. And so, I decided it was the right time to reboot that blog; time to record new tales but also to review the ones already told.

The C++ Lands: it's amazing creatures and weird beasts
[Credit for the map goes to Fearless coder: The C++11 Lands]

It's an exciting time for C++

A lot has happened in the last few years.

  • The C++0x standard was approved by ISO on 12 August 2011 and published soon after, finally turning that x into an 11. There have been no end of jokes about the x being hexadecimal, but the standard has came to be officially known as C++11. Popular implementations are rapidly including more and more features of this new standard, and the remaining ones are expected to arrive in the short future (check here for a regularly updated detailed list).

  • C++ now has a home on the web at isocpp.org, it both aggregates the best C++ content and hosts new content itself. There you can find about news, status and discussion about the C++ standard on all compilers and platforms.

  • We now have a Standard C++ Foundation, a non-profit organization whose purpose is to support the C++ software developer community and promote the understanding and use of modern Standard C++ on all compilers and platforms. Quoting from their webpage, two near-term goals of that mission are:

    1. To promote dissemination of correct and up-to-date information about modern C++.
    2. To promote greater availability of high-quality C++ libraries, including both standard libraries (by reducing barriers to submitting and adopting libraries in Standard C++ itself) and community libraries (by having an organized, and ideally tool-supported, way for C++ developers to discover and use libraries).

And there were even a few things that didn't happened —I'm looking at you, Concepts—.

What's new

We all know by now that Concepts, which were supposed to be the revolutionary new feature, didn't make it into the standard. There was no consensus whether concepts should be implicit or explicit, and no existing practice to steer the discussion in one way or another. The end result was that Concepts were deemed not ready for C++11.

So what is new? A plethora of useful features working together to change the way we write and think about code.

Move semantics

Rvalue references are non-const references to temporaries, also known as rvalues for often lying on the right side of an assignment. While they do make room for important optimizations —specially in those cases not covered by Copy Elision—, they are not a mere optimization but the foundational stone for implementing move semantics. Move semantics is how we naturally deal with objects in the real world. Think of it, if you need to move a chair from one place to another you just move it, you do not create a copy of the chair at the destination and then destroy the original chair. Move semantics let us state clearly and directly that our intention is to transfer an object's from one place to another, with the side effect that the operation will be as efficient as possible.

Even more, there is a subset of types for which making a copy makes no sense —think streams or the newly introduced std::thread—; objects of such types are uncopyable. This implies that their state used to be effectively tied to a particular instance, and their usability used to be limited due to moves being approximated by copies; i.e., they couldn't be held in containers. With move semantics, the state of an object can be detached from its instance, so that we no longer need to workaround artificial limitations by storing them in dynamic memory and dealing with them through pointers.

The previous paragraphs show us how move semantics make us think differently, but what about performance? Without move semantics, moving a value from one place to another entails making a copy of the original value and discarding the original value afterwards, which while logically correct is inefficient due to the unnecessary copies and destructions performed. Move semantics allow for stealing the internal resources of a temporary value, instead of duplicating them, given that the original resources will no longer be used afterwards. The notion has always been around in the Standard Library, embedded into algorithms that have to move objects from one place to another, albeit using swap. With move semantics, canonical swap involves no copies for movable objects —we don't intend to copy any object after all, just change their places—, and is now implemented like this:

template< typename T > 
void swap( T& a, T& b )
{
  T tmp = move( a );
  a = move( b );
  b = move( tmp );
}

Rvalue references, along with some changes to the wording for lvalue references, also allow for perfect forwarding. This is the possibility for a function to forward its arguments to some other function exactly as they were received; something that previously required 2^N overloads for the N-argument case.

template <class T, class A1, class A2>
shared_ptr<T> factory(A1&& a1, A2&& a2) {
  return shared_ptr<T>(new T(std::forward<A1>(a1), std::forward<A2>(a2)));
}

Generalized constant expressions

The notion of constant expressions has been generalized to include function calls. Limited recursion is allowed (up to at least 512 nested invocations), which does not cripple the computational power of constexpr functions in lue of the requirement for them to be of the simple form return expr;.

constexpr int square(int x)
    { return x * x; } // OK
constexpr long long_max()
    { return 2147483647; } // OK
constexpr int abs(int x)
    { return x < 0 ? -x : x; } // OK
constexpr void f(int x) // error: return type is void
    { /∗ ... ∗/ }
constexpr int prev(int x)
    { return --x; } // error: use of decrement
constexpr int g(int x, int n) { // error: body not just “return expr”
    int r = 1;
    while (--n > 0) r *= x;
    return r;
}

Simpler ways to write code

Type inference

Previously, the type of a variable had to be explicitly specified in order to use it. Now, the definition of a variable with an explicit initialization can use the auto keyword and its type will be deduced to the specific type of the initializer. Additionally, the decltype keyword can be used to determine the type of an expression at compile-time.

decltype(5) x = 5; // OK: x has type int
static decltype(0.0) y = 0.0; // OK: y has type double

auto x = 5; // OK: x has type int
static auto y = 0.0; // OK: y has type double
auto int r; // error: auto is not a storage-class-specifier

Range-based for-loop

The Range-based for-loop is syntactic sugar for iterating over a range. A range is either an array or a class that defines begin()/end() functions, either as member functions or as free functions discoverable by argument-dependent lookup.

int array[5] = { 1, 2, 3, 4, 5 };
for (int& x : array)
  x *= 2;

Lambda functions and expressions

Lambda expressions provide a concise way to create simple function objects. A lambda function can refer to identifiers declared outside the lambda function; the set of these variables is commonly called a closure.

void abssort(float *x, unsigned N) {
    std::sort(x, x + N,
        [](float a, float b) {
            return std::abs(a) < std::abs(b);
        });
}

default and deleted functions

The generation of special member functions can be explicitly defaulted or deleted.

struct noncopyable {
    noncopyable() = default; // default constructor
    noncopyable(const noncopyable&) = delete; // no copy-constructor
    noncopyable& operator=(const noncopyable&) = delete; // no assignment operator
};

Initializer lists

POD types can be initialized by means of an initializer-list —a construct inherited from C—, in which a struct or array is given a list of arguments in curly braces in the order of the members' definitions. This construct has been extended so that it can be used for all classes. It is bound to the std::initializer_list< T > template class, which is a lightweight proxy object that provides access to an array of objects of type T. When a constructor takes a single std::initializer_list argument its considered an initializer-list-constructor, a type of constructor treated specially during uniform initialization.

struct A {
  A(std::initializer_list<double>);
};
A a{ 1.0,2.0 };

Uniform initialization

There are several ways to initialize types, and they do not all produce the same results when interchanged. There is now a syntax that allows for fully uniform type initialization that works on any object; it expands on the initializer list syntax, in which a struct or array is given a list of arguments in curly braces in the order of the members' definitions. If a class has an initializer list constructor, then it takes priority over other forms of construction.

Variadic templates

Templates can take variable numbers of template parameters.

template<typename... Values> class tuple;

They also allows the definition of type-safe variadic functions.

// no parameters overload
template void printf(std::string const& format);

// generic overload, consumes one parameter and calls printf recursively
template< typename T, typename ...Params >
template void printf(std::string const& format, T&& value, Params&&... params);

Relaxed restrictions

The Plain Old Data (POD) concept was divided into two separate concepts: Trivial and Standard-Layout. With this separation it becomes possible to give up one concept without losing the other. A trivial type value can be statically initialized, and it is legal to copy it via memcpy. Its lifetime begins when its storage is defined, not when one of its constructor completes. A standard-layout type orders and packs its members in a way that is compatible with C. A POD type is one that is trivial, standard-layout, and for which all of its non-static data members and base classes are PODs.

struct N { // neither trivial nor standard-layout
  int i;
  int j;
  virtual ~N();
};
struct T { // trivial but not standard-layout
  int i;
private:
  int j;
};
struct SL { // standard-layout but not trivial
  int i;
  int j;
  ~SL();
};
struct POD { // both trivial and standard-layout
  int i;
  int j;
};

Unions are no longer restricted to POD types only, they can now contain objects that have a non-trivial constructor.

union U {
  int i;
  float f;
  std::string s;
};

More standard library components

  • Unordered associative containers: std::unordered_set, std::unordered_map, et.al.
  • Tuples: std::tuple, std::tie, et.al.
  • Smart pointers: std::unique_ptr, std::shared_ptr, et.al.
  • Function objects: std::function, std::bind, et.al.
  • Random number generation: std::uniform_int_distribution, std::poisson_distribution, et.al.
  • Regular expressions library: std::basic_regex, std::regex_match, std::regex_search, et.al.
  • Atomic operations library: std::atomic, std::memory_order, et.al.
  • Thread support library: std::thread, std::mutex, std::future, et.al.
  • Time utilities: std::duration, std::time_point, std::system_clock, et.al.
  • Type traits: std::is_integer, std::is_same, std::remove_const, et.al.
  • Compile-time rational arithmetic: std::ratio, et.al.

That's it?

This is by no means an exhaustive list! The C++ lands are wide, and only as we venture deep into them we discover both new idioms and new dark corner cases.

So hear what the tales have to say, and step into these lands...


This blog runs on Nibbleblog, a powerful engine for creation and manipulation of blogs; it's generated by Markdown, a powerful yet clean text-to-HTML conversion tool; and uses Syntax Highlighter, a fully functional self-contained code syntax highlighter, for its code snippets.