Advanced C++

Features of standard 2011

Máté Cserép

October 2015, Budapest

Already seen

  • Null pointers
  • Constant expressions (constexpr)
  • noexcept specifier
  • Deleted constructors
  • Default constructors
  • Delegated constructors
  • Smart pointers (unique_ptr, shared_ptr, weak_ptr)
  • Right value references
  • Move semantic
  • Multithreading
  • Variadic templates
  • Variable templates (C++14)

Overview

  • Automatic type deducation and decltype
  • Uniform initialization syntax
  • Strongly typed enums
  • Traits and their various usages
  • Compile-time checking with static_assert 
  • User-defined literals
  • Lamda expressions, captures

Auto typing

template<class T> void printall(const vector<T>& v)
{
  for (typename vector<T>::const_iterator p = v.begin(); p!=v.end(); ++p) 
     cout << *p << "\n";
}

template<class T> void printall(const vector<T>& v)
{
  for (auto p = v.begin(); p!=v.end(); ++p) 
     cout << *p << "\n";
}

When the type of a variable depends critically on template argument it can be really hard to write code without auto.

template<class T, class U> void (const vector<T>& vt, const vector<U>& vu)
{
  // ...
  auto tmp = vt[i]*vu[i];
  // ...
}

Decltype

void f(const vector<int>& a, vector<float>& b)
{
  typedef decltype(a[0]*b[0]) Tmp;
  for (int i=0; i<b.size(); ++i) {
      Tmp* p = new Tmp(a[i]*b[i]);
      /* ... */
  }
  /* ... */
}

We can deduce the type of an expression:

Initializer lists

std::intitializer_list<int> il = { 1, 3, 5, 1, 76, 8 };
for (int *p = il.begin(); p != il.end(); ++p)
{
  cout << *p << endl;
}

Arrays and the standard containers support reading data from initializer lists:

int t[] = { 4, 3, 6, 1 };

vector<double> v = { 1, 2, 3.456, 99.99 };

list<pair<string,string>> languages = {
        {"Nygaard","Simula"}, {"Richards","BCPL"}, {"Ritchie","C"}
};

map<vector<string>,vector<int>> years = {
        { {"Maurice","Vincent", "Wilkes"},{1913, 1945, 1951, 1967, 2000} },
        { {"Martin", "Ritchards"} {1982, 2003, 2007} }, 
        { {"David", "John", "Wheeler"}, {1927, 1947, 1951, 2004} }
}; 

Range for

void f(const vector<double>& v)
{
  for (auto x : v) cout << x << '\n';
  for (auto& x : v) ++x;  // using a reference to allow us to change the value
}
int x[] = { 1, 2, 4, 6};
for(int elem : x)
{
  cout << x << '\n';
}

You can iterate through anything like an STL-sequence defined by a begin() and end(). All standard containers can be used as a range, as can a string, an initializer list, an array, and anything for which you define begin() and end(), e.g. an istream.

The begin() and end() can be a member or a free-standing function to be called like x.begin() or like begin(x) (e.g. for arrays).

 

Enum class

enum Alert { green, yellow, election, red };
enum class Color { red, blue };

Traditional enum:

It would be nice to have a:

  • scoped and strongly typed enum with
  • no export of enumerator names into enclosing scope, and
  • no implicit conversion to int.

Enum class

enum Alert { green, yellow, election, red };
enum class TrafficLight { red, yellow, green };

Alert a = 7;                // error (as ever in C++)
Color c = 7;                // error: no int->Color conversion
int a2 = red;               // ok: Alert->int conversion
int a3 = Alert::red;        // error in C++98; ok in C++11
int a4 = blue;              // error: blue not in scope
int a5 = Color::blue;       // error: not Color->int conversion  
Color a6 = Color::blue;     // ok

Conversion:

enum class Color : char { red, blue };           // compact representation

enum class TrafficLight { red, yellow, green };  // by default, the underlying type is int

enum E { E1 = 1, E2 = 2, Ebig = 0xFFFFFFF0U };   // how big is an E?
                                                 // (whatever the old rules say;
                                                 // i.e. "implementation defined")

Specify the representation:

Enum class

enum class Color_code : char;     // (forward) declaration
void foobar(Color_code* p);       // use of forward declaration
// ... later ...
enum class Color_code : char { red, yellow, green, blue }; // definition

Support of forward declaration:

enum class Permission
{
  Execute = 1,
  Write = 2,
  Read = 4
};

Permission p = Permission::Execute | Permission::Write;
if(p & Permission::Read)
{
  // ... work ...
}

Usage as flags (plain enums also support):

Static assertion

const int x = 10;
class Test;

static_assert(x >= sizeof(Test), "hiba");

Compile time assertion of constant expressions:

int f(int* p, int n)
{
  static_assert(p == 0 ,"p is not null"); // error: static_assert() expression not a constant expression
  /* ... */
}

Static assertion

template <class T>
struct data_structure
{
    static_assert(std::is_default_constructible<T>::value,
                  "Data Structure requires default-constructible elements");
};

struct no_default
{
    no_default() = delete;
};

int main()
{
    data_structure<int> ds_ok;
    data_structure<no_default> ds_error; // compilation error
}

Check constructor existence:

Static assertion

template <class T>
void swap(T& a, T& b) {
    static_assert(std::is_copy_constructible<T>::value,
                  "Swap requires copying");
    static_assert(std::is_move_constructible<T>::value &&
                  std::is_move_assignable<T>::value &&
                  (!std::is_nothrow_move_constructible<T>::value ||
                  !std::is_nothrow_move_assignable<T>::value),
                  "Swap may throw");
    auto c = b;
    b = a;
    a = c;
}

struct no_copy {
    no_copy ( const no_copy& ) = delete;
    no_copy () = default;
};
 
int main() {
    int a, b;
    swap(a, b);
 
    no_copy nc_a, nc_b;
    swap(nc_a, nc_b);  // compilation error
}

Check proper move semantics support:

User defined literals

#include <iostream>
#include <complex>
using namespace std;

int main()
{
  complex<double> a(4, 2);
  a = 8;
  complex<double> c = 6.0i;
  cout << c << endl;
}

Motivation:

constexpr complex<double> operator "" i(long double d)  // imaginary literal
{
  return {0,d}; // complex is a literal type
}

Solution:

User defined literals should always be prefixed with _ (underscore)

E.g. this literal will ship in C++14

Lamda expressions

vector<int> v;
for(int i = 0; i < 10; ++i)
    v.push_back(i);

auto lamda = [](int n) { cout << n << " "; };
lamda(10);

for_each(v.begin(), v.end(), [](int n) { cout << n << " "; });
cout << endl;
return 0;

[]           lambda-introducer
(int n)   lambda parameter declaration
{ ... }     compound statement

Lamda Expressions

struct LambdaFunctor
{
    void operator() (int n) const { cout << n << " "; }
};

int main()
{
    vector<int> v;
    for(int i = 0; i < 10; ++i)
        v.push_back(i);

    for_each(v.begin(), v.end(), LambdaFunctor());
    cout << endl;
    return 0;
}

Lambda expressions define classes and construct objects.

The previous program is equivalent to:

Lamda Expressions

vector<int> v;
for(int i = 0; i < 10; ++i)
    v.push_back(i);

for_each(v.begin(), v.end(),
    [](int n)
    {
        cout << n ;
        if ( n % 2 )
            cout << ":odd ";
        else
            cout << ":even ";
    });
cout << endl;

A lamda expression can contain multiply statements:

Lamda Expressions

vector<int> v;
for(int i = 0; i < 10; ++i)
    v.push_back(i);

deque<int> d;
transform(v.begin(), v.end(), front_inserter(d), [](int n) {return n*n;} );
for_each(d.begin(), d.end(), [](int n) { cout << n << " "; });

cout << endl;

If the lambda expression has return value, than the return type is automatically deduced from that expression.

Lamda Expressions

vector<int> v;
for(int i = 0; i < 10; ++i)
    v.push_back(i);

deque<double> d;

transform(v.begin(), v.end(), front_inserter(d),
    [](int n) -> double  { return n / 2.0; } );

for_each(d.begin(), d.end(), [](double n) { cout << n << " "; });

cout << endl;

Sometimes it is not easy to deduce the return type or not the desire type is deduced. We can explicitly specify the return type:

Lamda Expressions

vector<int> v;
for(int i = 0; i < 10; ++i)
    v.push_back(i);

int x = 0;
int y = 0;
int z = 0;
cin >> x >> y;

// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
// [0, 1, 2, 8, 9, 3, 4, 5, 6, 7]

auto lamda = [x,y](int n) { return x < n && n < y; };
cout << sizeof(lamda) << endl;

v.erase(remove_if(v.begin(), v.end(),
                 [x,y](int n) { return x < n && n < y; }),
        v.end());

for_each(v.begin(), v.end(), [](int n) { cout << n << " "; });

cout << endl;

The [] lamda specifier means, that the lambda is stateless, but we can capture local variables.

Lamda Expressions

struct LambdaFunctor
{
public:
    LambdaFunctor(int a, int b) : m_a(a), m_b(b) { }
    bool operator()(int n) const { return m_a < n && n < m_b; }
private:
    int m_a;
    int m_b;
};

int main()
{
  vector<int> v;
  for(int i = 0; i < 10; ++i)
      v.push_back(i);

  cin >> x >> y;
  int x, y;

  v.erase(remove_if(v.begin(),v.end(), LambdaFunctor(x,y)), v.end());
  return 0;
}

Since we passed the x and y parameters by value, it is basically equivalent to the following: 

Lamda Expressions

vector<int> v;
for(int i = 0; i < 10; ++i)
    v.push_back(i);

int x = 0;
int y = 0;
cin >> x >> y;

v.erase( remove_if(v.begin(),v.end(),
                  [=](int n) { return x < n && n < y; }),
         v.end()
 );

for_each(v.begin(), v.end(), [](int n) { cout << n << " "; });

cout << endl;
return 0;

The [=] is the default-capture lambda introducer.
Captures all locals by value.

Lamda Expressions

vector<int> v;
for(int i = 0; i < 10; ++i)
    v.push_back(i * 2);

const int prev = 0;
for_each(v.begin(), v.end(),
        [&prev](int& r)
        {
          cout << "("  << prev << "," << r << ")" << endl;
          prev = r;
        });

for_each(v.begin(), v.end(), [](int n) { cout << n << " "; });

cout << "prev = " << prev << endl;

How can we capture by reference?

Examples:

[&]                  Capture everything by reference
// [=, &x, &y] Capture everything by value but x and y which by reference
// [&, x, y]      Capture everything by reference but x and y which is by value

Lamda Expressions

struct X {
    int s;
    vector<int> v;
    void print() const     {
        for_each(v.begin(), v.end(), [this](int n) { cout << n*s << " "; });
    }
};

int main() {
    X x;
    x.s = 2;
    for(int i = 0; i < 10; ++i)
        x.v.push_back(i);

    x.print();
    return 0;
}

Capturing the current object in a method:

  • The this pointer is always captured by value
  • You can implicitly capture this with [=]

Lamda Expressions

int ii = 5;

int main()
{
    vector<int> v;
    for(int i = 0; i < 10; ++i)
        v.push_back(i);

    int x = 0;
    int y = 10;

    auto f = [x, y](int n) { if (x < n-ii && n-ii < y)
                            cout << n << " ";
    };

    for_each(v.begin(), v.end(), f);
    cout << endl;

    ii = 1;

    for_each(v.begin(), v.end(), f);
    cout << endl;
    return 0;
}

Global variables and static data members could be used, but they are not captured!

Lamda Expressions

void doit(const vector<int>& v, const function<void (int)>& f)
{
    f(10);
    for_each(v.begin(), v.end(), f);
    cout << endl;
}

int main()
{
    vector<int> v;
    int i = 0;

    generate_n(back_inserter(v), 10, [&] { return i++; } );

    doit(v, [](int n) { cout << n << " "; });

    const function<void (int)>& ff = [](int n) { cout << n << " "; };
    doit(v, ff);

    return 0;
}

What is the type of a lamda function object?
std::function

Advanced C++: Features of standard 2011

By Cserép Máté

Advanced C++: Features of standard 2011

  • 105