Preparation

In this tutorial, we will transform a short program from legacy C++ into C++11, and learn a few of the new features along the way.

The tutorial is designed for you to follow along hands-on: We will alternate between meeting a feature and integrating it into our code. If you want to get your hands dirty, the presentation slides and code samples are available on


              git clone https://github.com/nikolausmayer/cpp11presentation
            

(please use GCC/g++ ≥ 4.7) but you are not required to do so.

Disclaimer

I am not an expert in C++(11). I may or may not be able to answer technical questions.

The code examples are designed to highlight specific language features, and may not always represent best code practices or sensible program design.

If you are using Boost, you may already know much of what follows.

C++11


A superficial, cherry-picking hands-on introduction not as scary as it sounds


Nikolaus Mayer

Overview

  • What is C++11?
  • auto
  • Range for loops
  • Smart pointers
  • Uniform initialization syntax
  • λ
  • Multiple return values
  • Miscellaneous

What is C++11 ...

  • C++11 is a C++ standard specification published in 2011
  • It replaces C++03 and is itself replaced by C++14 / C++17
  • C++11 adds to the core language and the Standard Library

What is C++11 ...

  • C++11 is a C++ standard specification published in 2011
  • It replaces C++03 and is itself replaced by C++14 / C++17
  • C++11 adds to the core language and the Standard Library

... how do I use it ...

  • Use GCC 4.7 or later for best results (or Clang)
  • Compile with -std=c++11 (-std=c++0x for GCC ≤ 4.6)

... and why should I?

  • We'll get to that
  • You don't lose anything (few exotic breaking changes*)


*) http://stackoverflow.com/a/6402166

Hands-On

  1. Make sure you have at least g++-4.7, and if not:
    sudo apt-get install g++-4.9 gcc-4.9
  2. If you didn't do it earlier, get the materials:
    git clone https://github.com/nikolausmayer/cpp11presentation
  3. In your favourite editor, open the Makefile in the repository’s code folder code/Makefile
  4. Add -std=c++11 to CXXFLAGS
  5. make to make sure it works. If you had to install a new g++, you might want to use CXX=g++-4.9 make

The elephant in the room: auto

  • The auto keyword is not new, but its meaning has changed
  • Before: Storage class specifier (cf. static, extern)
  • Now: Automatic type deduction:
    
    float almostpi = 3.14f;
    std::vector<SomeClass<SomeType> >::iterator it = myvec.begin();
                  
    can become
    
    auto almostpi = 3.14f;
    auto it = myvec.begin();
                    
  • Type deduction happens at compile time
  • Important: In general, auto ignores references and const/volatile (there are special cases)

Return type deduction: auto, decltype


template <typename T1, typename T2>
auto add(T1 t1, T2 t2) {
  return t1 + t2;
}
auto v = add(2, 3.14);
          
does not work. But with a trailing return type, it can:

template <typename T1, typename T2>
auto add(T1 t1, T2 t2) -> decltype(t1 + t2) {
  return t1 + t2;
}
auto v = add(2, 3.14);
            

“Trailing” return type?

decltype can do anything that auto can. Why not write...

template <typename T1, typename T2>
decltype(t1 + t2) add(T1 t1, T2 t2) {     // (a)
  return t1 + t2;
}
decltype(add(2, 3.14)) v = add(2, 3.14);  // (b)
              
  • (a) t1 and t2 are technically unknown at that point
  • (b) This is just cumbersome


C++14 (-std=c++1y in GCC 4.8, -std=c++14 in GCC≥4.9):

template <typename T1, typename T2>
auto add(T1 t1, T2 t2) {
  return t1 + t2;
}
              

With great power
comes great responsibility


int i = 5;
ProbabilityModel obj = m[i];
std::pair<int,int> p = m_hocos[0];
std::cout << obj.prob(p);
          

auto i = 5;
auto obj = m[i];
auto p = m_hocos[0];
std::cout << obj.prob(p);
          

int idx = 5;
auto model = prob_models[idx];
auto point = m_homogeneous_coords[0];
std::cout << model.prob(point);
          
auto is a double-edged sword. If you let it loose, it will destroy your code. Use it wisely, and keep in mind: Either declaration or handle must be informative.

Range-for loops

Accessing single elements is readable:

auto point = m_homogeneous_coords[0];
std::cout << model.prob(point);
          
But when iterating over e.g. a std::vector

for (unsigned int i = 0; i < m_homogeneous_coords.size(); ++i)
  std::cout << model.prob(m_homogeneous_coords[i]);
            
the default solution includes boilerplate code because we do not actually care about i. The “good” solution (which also works for e.g. std::map) is far worse still:

for (std::vector<std::pair<int,int> >::iterator it = m_...begin();
     it != m_homogeneous_coords.end();
     ++it)
  std::cout << model.prob(*it);
            

Range-for loops (cont.)

In Python, we could simply write

for point in m_homogeneous_coords:
  print(point)
          
which is infinitely more intuitive. Luckily, with C++11 the following is not only legal, but preferred:

for (auto point: m_homogeneous_coords)
  std::cout << model.prob(point);
            
Caveat: For const-correctness and good practice,

for (const auto& point: m_homogeneous_coords)
  std::cout << model.prob(point);
            
is the best solution. Please note that if you do need the index, the auto way does not work.

Boost incarnate: Smart pointers

Arguably one of Boost’s most well-known features, smart pointers make safe memory management trivial. Instead of

std::vector<myClass*> vec;
myClass* instance = new myClass(...);
vec.push_back(instance);
              
we can include the <memory> header and write

std::vector<std::unique_ptr<myClass> > vec;
vec.push_back(std::unique_ptr<myClass>(new myClass(...));
              
and when vec is destroyed, all of its elements are automatically and safely destroyed as well.

Boost incarnate: Smart pointers (con.)

The unique_ptr is useful if an instance is never shared and never copied. To get the full functionality of naked pointers (e.g. copyability), use shared_ptr instead.

std::vector<std::shared_ptr<myClass> > vec;
std::shared_ptr<myClass> myClass_ptr{0};
myClass_ptr = std::make_shared<myClass>(...);
vec.push_back(myClass_ptr);
            
Smart pointers can still be dereferenced using * and ->, as well as checked for not-null using

if (myClass_ptr) {...}
              
A unique_ptr has virtually no overhead; a shared_ptr has some due to e.g. reference counting (weak_ptr can help).

Uniform initialization syntax

If this line struck you as odd

std::shared_ptr<myClass> myClass_ptr{0};
            
then you are right. The correct way to write this used to be

std::shared_ptr<myClass> myClass_ptr = 0;
              
or

std::shared_ptr<myClass> myClass_ptr = {0};
              
which uses C++’s aggregate initialization syntax.
In C++11 the first version is correct and preferred. It is an example of the new uniform initialization syntax.

Pop quiz


class myClass;

void Func() {
  int foo(myClass());
}
            
What is foo?
  • An int variable
  • A prototype for a function that takes a myClass and returns an int
  • A prototype for a function that takes a function that returns a myClass, and returns an int
  • Undefined behaviour

This is the dreaded Most Vexing Parse or MVP.

Uniform initialization syntax (cont.)

C++11’s uniform initialization syntax solves this.

class myClass;

void Func() {
  int foo{myClass{}};
}
            
Now foo can only be a variable of type int. This is almost universally preferrable to legacy style. The only real restriction is that narrowing is no longer allowed:

int a(5.2);  // ok, a==5
int a{5.2};  // error!
              

Lambda

"Lambda" stands for (anonymous) function objects.

int main() {
  std::vector<float> numbers;
  ...
  auto multiplier = [&numbers](float factor) -> void {
    for (auto& elem: numbers) elem *= factor;
  };
  multiplier(3.0f);
}
            
multiplier is a function. It
  • captures the variable numbers by reference,
  • takes a float argument factor,
  • returns void (remember trailing return?),
  • and multiplies each element in numbers by factor.
auto is necessary because the type of a lambda function is complicated and inferred at compile time.

Lambda (cont.)

The C++11 syntax seems strange at first and allows for esoteric code:

[](){}();
            
is an in-place definition and immediate call of an anonymous void function which takes no parameters and does nothing. It is also completely legal.

Multiple return values

Python:

def argmin(sequence):
  min_index = ...
  min_value = ...
  return [min_index, min_value]

mini, minv = argmin(v)
              
C++:

template <typename T>
int argmin(std::vector<T>& v, 
           T& min_value) 
{
  int min_index = {...}
  min_value = {...}
  return min_index;
}
            

template <typename T>
void argmin(std::vector<T>& v, 
            int& min_index,
            T& min_value) 
{
  min_index = {...}
  min_value = {...}
}
            

Multiple return values (cont.)

C++11:

template <typename T>
std::tuple<int, T> argmin(std::vector<T>& v) 
{
  int min_index = {...}
  T min_value = {...}
  return std::make_tuple(min_index, min_value);
}

...
int mini;
float minv;
std::tie(mini, minv) = argmin(numbers);
              
std::tuple can take arbitrarily many elements and types.
If you do not care about some of the return values...

std::tie(mini, std::ignore) = argmin(numbers);
              

Checking for C++11

In our code, you will find

#if __cplusplus > 199711L
  std::cout << "Compiled using C++11 or later\n";
#else
  std::cout << "Compiled using C++03 or earlier\n";
#endif
            
Standard values for __cplusplus are 199711L (C++98/C++03 aka "C++"), 201103L (C++11) and 201402L (C++14).
Some compilers only support specific features, so it may be wiser to check for e.g.

#ifdef HAS_MOVE_SEMANTICS
              

Pop Quiz


              std::vector<std::vector<int>> vec;
            
What is wrong with this line? GCC says:

                error: '>>' should be '> >' within a nested template argument list 
              
Remedy:

                std::vector<std::vector<int> > vec;
              
C++11: Both versions are equivalent.

Compile-time assertions

Runtime assertions are useful

int stupid_division(int a, int b) {
  assert(b != 0);
  return (a / b);
}
            
but limited.

template <int N>
class Filter {
  ...
}
              
What if Filter is only valid for N>1?

Compile-time assertions (cont.)

Solution 1: Runtime assertion

template <int N>
class Filter {
  Filter() {
    if (N<=1) throw std::runtime_error("Filter invalid for N<=1");
  }
  ...
}
            
Solution 2: C++11’s compile-time (“static”) assertion

template <int N>
class Filter {
  Filter() {
    static_assert(N>1, "Filter invalid for N<1");
  }
  ...
}
              

and their trusty sidekicks, type traits


template <typename T>
T very_small_value() {
  return (T)0.00001;
}
            
What if T is an integer type?
Solution 1: Valid specializations plus fallback

template <typename T>
T very_small_value() {
  throw std::runtime_error("Bad type");
  return (T)0.00001;
}
template float very_small_value<float>()  { return 0.00001f; }
template double very_small_value<double>() { return 0.00001; }
...
              
Problem: Compiler might not issue a warning, and errors only appear at runtime.

and their trusty sidekicks, type traits


template <typename T>
T very_small_value() {
  return (T)0.00001;
}
            
What if T is an integer type?
Solution 2: Type trait check for floating-point-ness

template <typename T>
T very_small_value() {
  static_assert(std::is_floating_point<T>::value,
                "very_small_value only works for float types");
  return (T)0.00001;
}
            
Type traits are defined in the type_traits header:
is_enum, is_const, is_move_assignable...

Speaking of enum...

While enums have underlying types

Standard C++ 7.2/5:
  The underlying type of an enumeration is an integral type 
  that can represent all the enumerator values defined in the 
  enumeration.
            
they are effectively just integers.
They also expose their values which can lead to collisions:

enum boyfriend_type { rich, nice, cool };
enum CERN_magnet_status { warm, cool, supercool };
              
GCC complains:

error: redeclaration of ‘cool’
note: previous declaration ‘boyfriend_type cool’
              

Speaking of enum... (cont.)

C++11 offers scoped enumerations

enum class boyfriend_type { rich, nice, cool };
enum struct CERN_magnet_status { warm, cool, supercool };
...
int magnet = CERN_magnet_status::supercool;  // not ok
CERN_magnet_status magnet = CERN_magnet_status::supercool;  // ok
              
and strongly typed enumerations

enum class boyfriend_type : short { rich, nice, cool };
enum CERN_magnet_status : int { warm, cool, supercool };
              

Hashable types

In Python, type specific values can simply be stored in a dictionary (= map, hashmap):

type_values = {int: 1, float: 2}
            
C++ does not allow this easy way

std::map<std::typeinfo, int> type_values;
type_values[typeid(int)] = 1;
              
A popular but awkward workaround are templated structs:

template <typename T>
struct type_values { static const int value = 0; }

template struct type_values<int> { static const int value = 1; }
              

Hashable types (cont.)

C++11 still does not allow

std::map<std::typeinfo, int> type_values;
type_values[typeid(int)] = 1;
            
but if offers the hashable type_index:

std::map<std::type_index, int> type_values;
type_values[std::type_index(typeid(int))] = 1;
              

Matlab® vs C++11

  • no type declarations auto
  • automatic memory management ➞ smart pointers
  • not free ➞ free
  • interpreted ➞ not interpreted
  • call-by-value ➞ (smart) pointers, references

Many more things

C++11 offers much more:
decltype, initializer lists, constexpr, Constructors calling other constructors of the same class, override and final keywords, rvalue references, [[noreturn]] and other attributes, Variadic templates, std::nullptr_t, type traits, std::thread, std::promise, std::future, std::packaged_task, static_assert, std::chrono::steady_clock, class enum, std::I_hope_this_is_too_small_to_read, ...

C++14 offers even more, but compiler support may be incomplete (Clang is leading). C++17 is not yet finalized. YouTube offers many great CppCon talks on this topic.

Summary

  • C++11 takes away much of the C++ grind
  • There is no hard reason not to use it
  • On a remotely up-to-date system, it’s just a keyword away
  • But please, think before you auto

THE END

Thank you for you attention!

Questions?

Sources

  • stackoverflow.com, cppreference.com, en.wikipedia.org
  • https://www.ibm.com/developerworks/community/blogs/5894415f-be62-4bc0-81c5-3956e82276f3/entry/introduction_to_the_c_11_feature_trailing_return_types
  • http://www.codeproject.com/Articles/570638/Ten-Cplusplus-Features-Every-Cplusplus-Developer
  • http://thbecker.net/articles/auto_and_decltype/section_01.html


Images:
  • C++ timeline: isocpp.com
  • jesuschristhowhorrifying: horribleville.com
  • areyoufuckingkiddingme: knowyourmeme.com