Marc Wäckerlin
Für eine libertäre Schweiz

C++11 … 14 … 17 … 20

October 3, 2018

For all of you who have been programming C++ since ages, it makes sense to summarize the changes in C++ after the famous STL introduction in 1998, so let’s summarize the features of C++11, C++14, C++17. Later I’ll add C++20. All examples can be compiled.

Deprecation

Throw is Replaced by Noexcept

In C++11, exception declaration with throw has been deprecated and replaced by noexcept. Since C++17, throw declarations are deprecated considered as error, e.g. «error: ISO C++1z does not allow dynamic exception specifications»:

  • throw() is replaced by noexcept or noexcept(true)
  • throw(std::exception) is replaced by noexcept(false)

My usage recommendation:

  • If a class may throw an exception, don’t specify anything. If nothing is declared, you always must expect an exception and do exception handling. This has always been the case, now it’s clear.
  • If a class does not throw any exception, declare this with noexcept. So only if noexcept is declared, exception handling, i.e. a try-catch-block, is not needed.

Use grep and sed to change an entire project:

EXCLUDE='--exclude=*~ --exclude-dir=.cvs --exclude-dir=.git --exclude-dir=.hg --exclude-dir=.svn --exclude-dir=.boar'
/bin/grep ${EXCLUDE} -rl 'throw()' \
  | xargs -i{} sed -i 's,throw(),noexcept,g' {}
/bin/egrep ${EXCLUDE} -rl 'throw\([^()]+\)' \
  | xargs -i{} sed -i 's,throw([^()]\+) *,,g' {}

In addition and in the logical consequence, the unexpected handler is also deprecated.

Language Enhancements

Null Pointer

There exists keyword nullptr as null pointer, instead of 0 (NULL was always discouraged).

Type Calculation and Automatic Detection

If the type of a variable can be determined by the compiler from the initialization value, you can simply specify it as auto. Auto always generates non-reference-types otherwise use e.g. decltype(auto) or auto&.

std::string s("hello world");
auto it(*s.begin()); // it is the first char

Using decltype, the type can be calculated from an expression. Other than auto, decltype can deduce reference types.  It can be combined to decltype(auto).

int i;
decltype(i) j1 = i; // takes a copy of i
decltype((i)) j2 = (i); // takes reference &i

In C++11, a calculated function return type must be declared with -> decltype(…).

template<typename T1, typename T2> auto mult(T1 a, T2 b) -> decltype(a*b) {
  return a*b;
}
…
unsigned long b(232);
long double a(-1.000100010001);
decltype(a*b) c = mult(a, b);

Since C++14, the return type of functions can be detected automatically and decltype is no more necessary.

template<typename T1, typename T2> auto mult(T1 a, T2 b) {
  return a*b;
}

Structured Binding

#include <iostream>
#include <tuple>

int main(int, char const *const *const) {
  // «old» C++11-style
  char a(' ');
  int b(0);
  bool c(false);
  std::tie(a, b, c) = std::make_tuple('a', 123, true);
  std::cout<<"a="<<a<<", b="<<b<<", c="<<(c?"true":"false")<<"\n";
  // new C++17-style (g++ requires option -std=c++1z)
  auto [d, e, f] = std::make_tuple('x', 432, false);
  std::cout<<"d="<<d<<", e="<<e<<", f="<<(f?"true":"false")<<"\n";
  return 0;
}

Constant Expression

Constant expressions are now more flexible, i.e. a constant expression can be marked explicitly with constexpr keyword.

constexpr double pi = 3.1415926535897932384626433;

Static Assertion

Template parameters can now be tested with static_assert. The error message is optional since C++17.

static_assert(constant-expression, error-message);

Alignment

You can specify and query alignment with the new keywords alignas and alignof.

alignas(float) unsigned char c[sizeof(float)]

Inline Variables

Variables can be specified inline since C++17.

External Templates

Templates can now be declared extern, so that they are not instantiated in a specific translation unit.

Loops on Container

For loops on container can be specified as:

for (iterator-type loop-variable: container-variable) {statements}

To iterate over a string:

std::string s("Hello World");
for (auto const &c: s) {
  std::cout<<"character: "<<c<<"\n";
}

With compound collections, such as maps you can use structured binding:

std::map<std::string, std::string> map = {{"hello", "world"}, {"foo", "bar"}};
for (auto&[key, value]: map) {
  std::cout<<"element: "<<key<<" = "<<value<<"\n";
}

Initialisation in if and case

The structures if and case now may have an initialization section, similar to for.

if (int i=1; b<1) …
switch (auto[a,b]=fn(); a) {…}

Template Deduction

Now template parameters can be deduced automatically.

std::pair p(5.0, 'x');

Brace-Initialisation

You can initialize everything using curly-braces:

struct X {
    int a;
    int b;
    X(): a{0}, b{0} {}
    X(int _a, int _b): a(_a), b(_b) {}
};
X x{4, 5};
std::vector<std::string> v{"hello", "world", "foo", "bar"};
int* a = new int[3] {1, 2, 3}

Typesafe Enums

Enumerations are now strongly typed and it is possible to specify any integer type specifier. Adding keyword class or struct creates the constants for the values within the namespace of the enumeration.

enum X: short {A = 15, B = 30}; // use as X a(A);
enum class Y: short {A = 15, B = 30}; // use as Y a(Y::A);

Neested Namespaces

Write namespace X::Y { … } instead of namespace X { namespace X { … } }.

Non-Trivial Unions

Unions may now contain no-trivial objects, if the union has a correspondig constructor and assignment-operator.

String Constants and Literals

Declare UTF-8 string constants (and since C++17 also single chars) as u8"中文" in char or UTF-16 string constants as u"更多的中文" in char16_t or UTF-32 as U"还不够啊!" in char32_t. And there exist raw strings with arbitrary delimiter no longer than 16 characters, such as R"arbDelim(this "is" raw)arbDelim", which can be combined, e.g. u8R"(my raw string)".

There is now a 0b or 0B literal for binary numbers, such as 0b101000110. Numbers can be divided with ', such as 1'000'000.

Use literal s for std::basic_string, such as “hello”s.

Use literals h, min, s, ms, us, ns for time in std::chrono::duration. This requires using namespace std::literals;.

Complex numbers can be written with i, if or il for std::complex<double>, std::complex<float> and std::complex<long double>.

Hexadecimal literals exist also as float.

Own Literals

Overwriting operator “” you can define your own literals.

#include <iostream>
#include <string>
#include <cctype>
#include <algorithm>

std::string operator "" _U(char const *literal, size_t len) {
  std::string res(literal, len);
  std::transform(res.begin(), res.end(), res.begin(), toupper);
  return res;
}

int main(int, char const *const *const) {
  std::string name = "Marc"_U;
  std::cout<<"name="<<name<<std::endl;
  return 0;
}

Template Typedefs

Finally, template typedefs are possible with keyword using.

template<typename A, typename B, typename C> class X …;
template<typename B> using Y = X<int, B, float>;

Template Variables

Since C++14, variables can be templated too.

#include <iostream>
#include <iomanip>

template<typename T> T pi = T(3.141592653589793238462643383);
template<> const char* pi<const char*> = "3.141592653589793238462643383 -> PI";

int main(int, char const *const *const) {
  std::cout<<std::setprecision(40)<<pi<const char*><<"\n"
           <<pi<float><<"\n"<<pi<double><<"\n"<<pi<long double><<"\n";
  return 0;
}

Check Include File Availability

Use __has_include to check for the availability of include files.

Lambda Expression

A lambda expression is an unnamed inline function, which is especially useful within algorithms. It is used as:

[used external variables, variable = value](parameter){function body}
auto lambda = [value = 1] {return 2*value;};
std::unique_ptr<int> ptr(new int(10));
auto lambda = [value = std::move(ptr)] {return *value;};

In old C++ versions, you would have to define a separate function. As an example, let’s count the non-space characters in a string and create a copy of all characters in the string, that are not white spaces. Without lambdafunctions, this could be:

#include <iostream>
#include <string>
#include <cctype>

bool appendIfNotSpace(unsigned char const c, std::string& s) {
  if (std::isspace(c)) return false; // don't count
  s += c;
  return true;
}

int main(int, char const *const *const) {
  std::string s("Hello World,\nthis is\ta text.");
  std::string textWithoutSpaces; // fill in text without spaces
  unsigned int cnt(0);
  for (auto c: s) {
    if (appendIfNotSpace(c, textWithoutSpaces)) ++cnt;
  }
  std::cout<<"«"<<s<<"»\ncontains the following "<<cnt<<" non-space-characters: "
           <<textWithoutSpaces<<"\n";
  return 0;
}

With lambda-functions, this becomes much simpler, we can use the function std::count_if and pass textWithoutSpaces as additional external reference:

#include <iostream>
#include <string>
#include <cctype>
#include <algorithm>

int main(int, char const *const *const) {
  std::string s("Hello World,\nthis is\ta text.");
  std::string textWithoutSpaces; // fill in text without spaces
  unsigned int cnt =
    std::count_if(s.begin(), s.end(),
                  [&textWithoutSpaces](auto c) {
                    if (std::isspace(c)) return false; // don't count
                    textWithoutSpaces += c;
                    return true;
                  });
  std::cout<<"«"<<s<<"»\ncontains the following "<<cnt<<" non-space-characters: "
           <<textWithoutSpaces<<"\n";
 return 0;
}

Bind Functions and Reference Wrapper

Use std::bind to create  a function object and use std::placeholders::_1, std::placeholders::_2, … as placeholder to bind the first parameter, the second , and so on, to the function. Use std::ref to pass an argument by reference.

#include <iostream>
#include <functional>

void test(int a, int b) {
  std::cout<<"a="<<a<<"; b="<<b<<"\n";
}

int main(int, char const *const *const) {
  auto fn1a = std::bind(test, std::placeholders::_1, 5);
  auto fn1b = std::bind(test, 5, std::placeholders::_1);
  auto fn2 = std::bind(test, std::placeholders::_1, std::placeholders::_2);
  fn1a(4);
  fn1b(2);
  fn2(8, 9);
  return 0;
}

R-Value References

R-value references can bind to r-values, such as temporary objects or literals. They are especially useful to implement a move-semantic, when a resource is moved from one object to another, leaving the original object empty. A class can have a move constructor and a move assignment to take ownership of temporary ressources:

#include <iostream>
#include <iterator>

struct X {
    int *array = nullptr;
    int size = 0;
    X(int sz, int* a): size(sz), array(a) {
      std::cout<<"array\n";
    }
    X(X const &o): size(o.size), array(new int[o.size]) {
      std::cout<<"copy\n";
      std::copy(o.array, o.array+size, array);
    }
    X(X&& o): size(o.size), array(o.array) {
      std::cout<<"move\n";
      o.array = nullptr;
      o.size = 0;
    }
    ~X() {
      std::cout<<"delete "<<size<<" elements from "<<array<<"\n";
      if (array) delete array;
    }
    void print() {
      std::copy(array, array+size,
                std::ostream_iterator<int>(std::cout, ", "));
      std::cout<<std::endl;
    }
};

// dummy copy function
// to prevent compiler optimization
// this enforces move semantics
X cp(X x) {
  return x;
}

int main(int, char const *const *const) {
  X x{5, new int[5]{1, 2, 3, 4 ,5}};
  x.print();
  X x1{x}; // copy constructor
  x1.print();
  X x2{cp(X{4, new int[4]{0, 2, 4, 8}})}; // move
  x2.print();
  return 0;
}

Variadic Templates

You find a nice example for variadic templates in C++11 Variadic Templates: Fun with Tuple.

#include <iostream>

template <typename... T> struct X {
};

template <typename Value, typename... T> struct X<Value, T...> {
    Value value = Value();
    X<T...> next;
    X(Value v, T... args): value(v), next(args...) {}
};

int main(int, char const *const *const) {
  X<int, float, int> x1(1, 2.5, 4);
  std::cout<<x1.value<<", "<<x1.next.value<<", "<<x1.next.next.value<<"\n";
  return 0;
}

Attributes and Deprecation

You can specify attributes. Predefined, there’s [[deprecated]] and since C++17  [[fallthrough]] used in case without break, [[maybe_unused]] for unused function parameters, [[noreturn]] if a function throws an exception before it returns, [[carries_dependency]] and [[nodiscard]] .

[[deprecated("replaced by y()")]] void x();

Enhancements for Classes

In-Class-Initialisation

Variables can now be initialized directly in the class declaration:

struct X {
    int a{0};
    int b = 1;
};

Deleted and Defaulted Functions

Functions can be declared to be deleted or to be implemented using the default implementation:

struct X {
    X() = default; // generates default constructor
    X(X const&) = delete; // prohibits copy constructor
};

Calling Constructors

Since C++11, a constructor can call another constructor:

struct X {
    X() {std::cout<<"X()\n";}
    X(int): X() {std::cout<<"X(int)\n";}
};

Inherited Constructors

All constructors of a base class can be  inherited with using.

struct X {
    X(int) {}
};
struct Y: X {
    using X::X; // inherit all constructors
};

Explicit Overwrite and Final Implementation

If you overwrite a derived method and use keyword overwrite, then the signature is checked by the compiler. If the signature is not available in the parent class, an error is generated, rather than just creating a new method.

Add keyword final to a class to prohibit further inheritance or to a method to forbid overwriting it in derived classes.

Explicit Conversion

The keyword explicit can now be used in conversion operators. This way, a bool-operator cannot be used in an non-boolean context.

struct X {
    explicit operator bool();
};

Library Enhancements

Function Object

There is a new std::function class that represents any kind of function or functor. This way, a collection can store any kind of callback functions. Example:

#include <iostream>
#include <functional>
#include <unordered_map>

bool test(int a, int b) {
  return a<b;
}

struct functor {
    bool operator()(int a, int b) {
      return a>b;
    }
};

int main(int, char const *const *const) {
  functor fn; // just an instance of the functor
  // store some functions in a vector (use the new initializer)
  std::unordered_map<std::string, std::function<bool(int,int)>> v = {
    {"smaller", test},
    {"bigger1", fn},
    {"bigger2", functor()}, // another instance of functor
    {"equal", [](auto a, auto b){return a==b;}} // lambda function
  };
  // generate some combinations of numbers
  for (int a(0); a<3; ++a) for (int b(0); b<3; ++b) {
      std::cout<<"---------------------------------\n";
      // now, apply all functions to the a/b-combination
      for (auto&[name,function]: v)
        std::cout<<name<<"("<<a<<","<<b<<") = "
                 <<(function(a,b)?"true":"false")<<"\n";
    }
  return 0;
}

Regular Expressions

You can define regular expressions in std::regex. Use std::regex_match to check if a whole string matches the expression, or use std::regex_search to find the first ocurance of a match within a string. To get all matches from a string, use a token iterator. Example:

#include <iostream>
#include <string>
#include <regex>

int main(int, char const *const *const) {
  std::string s("string to match, e.g. number 13,8421 or -421, other text");
  std::regex number("(\\+|-)?[[:digit:]]+(,[[:digit:]]+)?");
  std::smatch match;
  if (std::regex_search(s, match, number)) { // only first match is found
    for (auto m: match) std::cout<<"expression: "<<m<<"\n"; // sub expressions
    // to iterate over all numbers in the string, use a token iterator
    for (std::sregex_token_iterator it(s.begin(), s.end(), number);
         it!=std::sregex_token_iterator();
         ++it)
      std::cout<<"found number: "<<*it<<"\n";
  }
  return 0;
}

Threads

Finally C++ can do native threads, just pass a function object to std::thread, then detach or join it. Compile with option -pthread. Lock access using std::lock_guard<std::mutex>. Use std::packaged_task  together with std::future to get the return result of a thread. Use std::promise with std::future and std::condition_variable to send signals between threads.

#include <iostream>
#include <thread>
#include <mutex>
#include <future>
#include <functional>

using namespace std::literals; // to use literal ms for milliseconds
std::mutex cout; // mutex to lock access to std::cout

struct X {
    std::string name;
    void operator()() {
      for (int i(0); i<10; ++i) {
        std::this_thread::sleep_for(10ms); // same as std::chrono::milliseconds
        std::lock_guard<std::mutex> write_cout(cout);
        std::cout<<name<<':'<<i<<' '<<std::flush;
      }
    }
};

unsigned long factorial(unsigned long n) {
  if (n==0) return 1;
  return n*factorial(n-1);
}

int main(int, char const *const *const) {
  std::promise<void> exit; // signal without value
  std::packaged_task<unsigned long()> task(std::bind(factorial, 20)); // async call factorial(20)
  std::future<unsigned long> f1 = task.get_future(); // future result of task
  std::thread t1(X{"one"}), t2(X{"two"}), t3(X{"three"}), t(std::move(task)),
    t4([&exit](){
        unsigned long i{0};
        auto future(exit.get_future());
        while(future.wait_for(5ms) // same as std::chrono::milliseconds(5)
              == std::future_status::timeout) {
          std::lock_guard<std::mutex> write_cout(cout);
          std::cout<<"tick:"<<++i<<' '<<std::flush;
        }
      });
  t1.detach(); // one
  t2.detach(); // two
  t3.join(); // three
  t.join(); // factorial calculation task
  f1.wait(); // wait for the task result
  exit.set_value(); // trigger exit future
  t4.join(); // lambda ticks
  {
    std::lock_guard<std::mutex> write_cout(cout);
    std::cout<<"\nresult of task: "<<f1.get()<<"\n"; // not guaranteed at the end
  }
  return 0;
}

New Container Classes

There are new unordered container-classes: std::unordered_set, std::unordered_map, std::unordered_multiset, and std::unordered_multimap.  The content of the containers is not sorted, but organized in buckets refernced by hashes. This allows faster access.

A tuple can hold a given number of values of a given type, e.g.:

std::tuple<int, std::string, char, float> t{1, "hello", 'x', 3.14159};

New Shared Pointer

std::unique_ptr  is a smart pointer with unique object ownership. It replaces std::auto_ptr. std::shared_ptr is a smart pointer with shared object ownership. std::weak_ptr holds a thread safe temporary (weak) reference to a std::shared_ptr.

std::make_unique does for std::unique_ptr what std::make_shared does for std::shared_ptr.

New Algorithms

Check if std::all_of, std::none_of or std::any_of the values in a container match a criteria. Test if any character is uppercase:

std::string s{"a text with an Upper case character"};
if (std::any_of(s.begin(), s.end(),
                 [](char c){return std::isupper(c);}))
  …

Copy a number of elements with std::copy_n. std::iota creates a range of sequentially increasing values.

Random Numbers

Library random provides three random number generators and many distributions.

Filesystem

Library filesystem implements filesystem functionality.

Iterators

In addition to std::begin and std::end, there is now also std::cbegin and std::cend for constant iterators, std::rbegin and std::rend for reverse iterators and std::crbegin and std::crend for the combination of constant and reverse.

std::equal, std::mismatch or std::is_permutation check iterators and intervals.

Traits

The new library type_traits allows static checks, such as std::is_integral<T1>::value to implement constraints or optimize template implementations. std::is_final checks whether a class is final.

There are logical operator traits std::conjunction, std::disjunction and std::negation.

Quoted Streams

Manipulator std::quoted handles quoted streams.

Exchange Values

Exchange values with std::exchange.

Special Types

std::string_view is a read-only non-owning reference to a character sequence. std::byte holds a byte.

std::optional represents optional variables, std::any may hold any type, std::variant is a tagged union container.

Uniform Container Access

Uniform container access with std::size, std::empty, std::data.

Kommentare