"Modern" C++ Features
Erich Keane
Software Engineer, Intel C/C++ Compiler Front End
Erich.Keane@intel.com
What is Modern C++?
Plus Presentation Basics:
- For the purposes of this presentation, C++11 and newer
- This presentation covers up to the DIS for C++17
- A number of things have been greatly simplified/glossed over. Treat this presentation as a starting point on 'what to google', not an authoritative source
C++11
nullptr
Typesafe version of "NULL". Implicitly converts to ANY pointer type, but not to a number.
void g(int* ptr);
void f(int val); // #1
void f(int* ptr); // #2
// NULL is often a cast to a void*, so it can convert to ints:
#define NULL ((void*)0)
g(NULL);
g(nullptr); // Same meaning!
f(nullptr); // calls #2!
f(NULL); // Calls #1!
// Note: many implementations now do:
#define NULL nullptrMove
Alternative for "copy" that allows you to 'steal' contents of a temporary:
class X {
char* myData;
int dataLen;
public:
// Copy CTOR
X(const X& other):dataLen(other.dataLen) {
myData = malloc(dataLen);
memcpy(myData, other.myData, dataLen);
}
// Move CTOR, language guarantees "other" is a temporary,
// thus no longer needs its data anymore, so we steal it!
X(X&& other) : dataLen(other.DataLen) {
myData = other.myData;
other.myData = nullptr;
}
};Auto Type Deduction
At compile time, the language can "figure out" types declared with "auto".
auto x = 7; // exactly the same as "int x = 7;"
// "p" deduced to return type of begin
for (auto p = v.begin(); p != v.end(); ++p){}
// Useful when you don't have a good way of knowing the type!
template<typename T, typename V>
void foo(T t, V v) {
auto value = t + v; // How would you name "value" ?
...
}Range-for
A new form for "for" loops over things with iterators.
// Loops over "v", making "x" a copy of the dereferenced iterator.
for (auto x : v)
...
// Or, save yourself a copy(or allow editing inside the loop)
// by making 'x' a reference.
for (auto &x: v)
...Right Angle Brackets
Collections of collections used to not parse right, its now fixed!
list<vector<string>> lvs;
^
// What is the >> above? Used to be ambiguous,
// since it could be shift. All the compilers "knew"
// anyway what you were doing, so you can type them
// next to each other.
// Standards compliance used to require:
list<vector<string> > lvs;Default/Deleted functions
Function bodies can be replaced with a simple alternative:
class X {
// Copy operator isn't an allowable function call!
X& operator=(const X&) = delete;
// Can also delete normal functions, makes them unavailable for lookup:
void foo(long long){...} // can call with a long long!
// Disallows this overload, also prevents conversion of long long to short.
void foo(short) = delete;
// The destructor's body is exactly what the compiler would generate
// if you hadn't said anything. Useful when creating a constructor
// would stop auto generation of a copy/move/etc.
~X() = default;
// Works for default ctor, dtor, copy/move/etc.
X(const X&) = default;
};Enum Class
Strongly typed and fully scoped enum!
enum class Color {red, blue};
Color c= 7; // error, doesn't convert to an int!
int i = Color::red; // Error, doesn't convert to int!
// Also note the fully scoped nature!
Color c2 = blue; // illegal, 'blue' needs to be scoped.
// Allows forward decl!
enum class Animal;
...
enum class Animal { lion, tiger, bear}; // Oh my!constexpr
constexpr Variables are calculated at compile time!
constexpr functions follow specific rules that allow them to generate constexpr variables (normally executed otherwise).
// Function can be calculated compiletime OR runtime
// Is compiletime if all of its parameters are constexpr
constexpr int Foo(int a, int b) {...}
void bar(int i, int j) {
// Calculated at compiletime, no execution time cost!
constexpr int B = Foo(5,3);
// Legal, calculated at runtime
int C = Foo(i,j);
// illegal, can't be calculated at compiletime!
constexpr int D = Foo(i,j);
// OK, uses only constexpr parameters
constexpr int E = Foo(B, 5);
}
Lambda Expressions
Creates an closure (aka, unnamed function, many others).
// Syntax (incomplete):
// [capture list] (parameters) -> ret_type {body}
// Parameters, and ret_type are optional in some cases.
// temp is a function-type
auto temp = [](int a, int b)->int { return a + b; };
// That you can call!
int value = temp(5,4);
int something_else = 5;
// something_else captured by 'copy', value is captured by reference.
auto captured= [something_else, value&]() {value = value = something_else};
captured();
// "this" captures (*this) by reference
auto p = [this](){this->foo = 5;}; // allowed
// [&] captures all automatic variables used by reference.
// [=] captures all automatic variables used by copy.decltype
Produces the 'declared type' of a variable or expression in a 'type' context:
struct A { double x; };
const A* a = new A{0};
decltype(a->x) y; // type of y is double (declared type)
template <typename T>
void Foo(T t) {
// Both are the same type!
auto temp = t.begin();
decltype(temp) other_temp = temp;
auto f = [](int a, int b) -> int { return a * b;};
decltype(f) g = f; // 'g' is the otherwise unnameable type of the lambda.
}Braced initialization
Alternate constructor syntax, makes things a bit less ambiguous. Makes construction like array initialization.
Foo f (5,7); // calls a ctor with 2 ints.
Foo f2 {5,7}; // Does the same thing.
Foo f3 {}; // Default constructs f3
Foo f4(); // Most vexing parse :)
int i{}; // Initializes to 0, otherwise would be uninitialized.
// TONS more rules here, please look this up!initializer_list
"Other" types can take array-like construction by creating an initializer_list ctor.
vector::vector(std::initializer_list<T> s){ // list of T ctor
//'s' can be iterated, or checked for size, nothing else
// ie: has begin,end,size, no indexing!
...
}
vector::vector(size_type count, const T& value = T()){...}
vector<int> v {1,2,3,4}; // creates a vector with these elements.
// Note that braced initialization prefers non-initializer-list
vector<int> v2 {7}; // calls 'count' CTOR. v2 is length 7.
vector<int> v3 {7,5}; // calls 'count', v3 is length 7, all values are 5.
vector<int> v4 {{7,6}};// forces initializer_list ctordelegating constructors
Constructors can finally call other constructors!
class X {
int a;
// Default ctor calls 'int' ctor with value 42
X() : X(42) {}
X(int x) : a(x) {}
};static_assert
Compile-time assertions
template<typename T>
void foo(T t) {
static_assert(sizeof(long)>=8, "Long must be 8 bytes for this library to work!");
static_assert(is_integral<T>::value, "Parameter must be an integral type!");
}
// NOTE: C++17 allows omitting 2nd parameter.Suffix Return Type
Similar to lambdas, return type can be put 'after' the function parameters:
template<typename T, typename U>
auto mul(T t, U u) ->decltype(t + u)
{ return x * y; }
auto add(int a, int b) -> int { return a + b;}Template alias
Better alternative to typedef!
using IntVec = std::vector<int>; // more clear as to what it does!
typedef void (*PFD)(double); // PFD is a pointer to a void(double) function, yucky!
using PF = void(*)(double); //more readable!
// Can be templated!
template<typename T>
using Collection = vector<list<T*>>;
Collection<int> c;
Collection<float> cf;Variadic Templates
Allows variadic functions to be implemented in a typesafe manner!
template<typename T, typename... Args> // note the "..."
void printf(const char* s, T value, Args... args) // note the "..."
{
while (s && *s) {
if (*s=='%' && *++s!='%') { // a format specifier (ignore which one it is)
std::cout << value; // use first non-format argument
return printf(++s, args...); // ``peel off'' first argument
}
std::cout << *s++;
}
throw std::runtime error("extra arguments provided to printf");
}User Defined Literals
Allows users to create user versions of "1.2F" type syntax:
// A good example:
auto t = "Hi!"s; // is a std::string
// User defined ones (non library) must begin with a "_"
struct Distance {
//...
void operator"" _km(long double);
void operator"" _mi(long double);
};
auto x = 5.5_km; // creates a "Distance" object in a user defined manner.
auto y = 5.5_mi; // creates a "Distance" object in a user defined manner.
// Allowable signatures:
( const char * )
( unsigned long long int )
( long double )
( char )
( wchar_t )
( char16_t )
( char32_t )
( const char * , std::size_t )
( const wchar_t * , std::size_t )
( const char16_t * , std::size_t )
( const char32_t * , std::size_t )Attributes
Standards version of __attribute__/__declspec
Very few builtin, but compilers extend.
void f [[noreturn]]();
void g [[gnu::force_inline]](); // compiler extensionnoexcept
Declare a function as non-throwing!
void foo() noexcept; // I declare this function doesn't throw!
// note, if my implementation of foo were to throw anyway,
// std::terminate would be called.
// Conditional noexcept:
template<typename T>
void bar(T& coll) noexcept(v.at(0)){ // Can only throw if v.at(0) does
for (int i; i < coll.size(); ++i) v.at(i) = "bar";
}override/final
Better errors for virtual function call overriding.
struct Base {
virtual void f();
virtual void g() const;
virtual void h(char);
void k(); // not virtual!
virtual void m();
};
struct D : Base {
void f(); // properly overrides
void f() override; // same
void g(); // surprisingly, doesn't override
void g() override; // error, wrong type!
virtual void h(char); // overrides
void k() override; // error, nothing to override!
virtual void m() final; // cannot override, error to try.
};std::array
A better/free 'array' implementation:
// Acts like a C array, but can be properly passed to functions with size!
std::array<int, 7> a;
int[] b;
void foo(int[] b) { cout << sizeof(b);} // always prints sizeof(ptr)!
void bar(std::array<int, 7>& a); // pass by reference, a.size is 7!Smart pointers
RAII container replacements for pointers:
// A type that doesn't allow copying, so it is the ONLY owner!
// Can be 'returned', since that causes a temporary, thus
// gets moved from.
// Guarantees it is destroyed when it goes out of scope!
std::unique_ptr<Foo> f = make_unique<Foo>(1,2,3);
// A type that reference-counts, last one out deletes the object!
std::shared_ptr<Foo> s = make_shared<Foo>(1,2,3);
// A 'weak' pointer, can only be acquired from a shared_ptr,
// and can only be used to convert to a shared_ptr:
std::weak_ptr<Foo> weak = s;
// .. more stuff, 's' may have deleted its things!
// Lock returns nullptr if the shared_ptr no longer exists.
if (shared_ptr<Foo> temp = weak.lock()) {
// Do stuff with 'temp'
} else {
// The foo has been destroyed already!
}
Other useful types:
std::thread // RAII container for threads!
// RAII contains for mutexes/CVs
std::mutex
std::timed_mutex
std::recursive_mutex
std::condition_variable
// RAII containers for locking/unlocking mutexes!
std::unique_lock
std::lock_guard // same as above, allows ownership transfer.
// Tons of time handling stuff
<chrono>, includes clocks, time_point, conversions, etc.
// Templated, creates atomic versions of
std::atomic
// "simpler" task spawning
std::future/std::shared_future/std::atomic_future
std::promise
std::packaged_task
std::async
<random> // Safer/better RNG, allows distribution and engine selection.
std::regex // Regular expressions!C++14
C++14 in a nutshell:
Relaxed rules for a large number of C++11 features, plus a few others!
//Return type deduction: Trailing return types can be omitted in some cases.
auto square(int n) {return n + n;}
//Constexpr expanded: Now much of the language is allowed
// in "constexpr" functions (not just return statements!).
// Generic lambdas!
auto mult = [] (auto a, auto b) { return a * b; };
// Lambda named captures:
// 'p' is the moved from 'p', 'q' is the name for a
// copy of 'bar' in the scope of the lambda.
auto lambda = [p = move(p), q = bar]{};
// constexpr variable templates!
template <typename T>
constexpr T pi = T(1415926535897932385);
// attribute deprecation!
class [[deprecated]] old {};
[[deprecated]] void foo();
// Binary Literals:
int val = 0b1234;
// Digit Separators:
float one_million = 1'000'000.00;C++14 builtin User Defined Literals:
// standard user-defined literals:
//"s" for string:
auto str = "hello world"s;
// "h", "min", "s", "ms", "us", "ns" for std::chrono::duration:
auto dur = 1230s;
// "if", "i", "il" for std::complex<float>,std::complex<double>,
// std::complex<long double>
auto cplx = 1i;
C++17
Some Removals:
Things that are deprecated or entirely removed:
- Trigraphs removed
- 'register' keyword now meaningless/semantic-less
- auto_ptr, random_shuffle, old parts of <functional> gone!
C++17, minor changes:
A few small, but useful changes.
// Variadic template fold-expressions:
template <typename ...Args> auto f(Args ...args) {
return (0 + ... + args); }
// Template auto for non-type parameters:
template <auto Size> struct S;
// Class template argument deduction, CTORs now deduced!
pair p(1,'x'); instead of pair<int, char> p(...);
// Inline variables. The following has all definitions refer to the same entity:
inline int n = 10;
// constexpr lambdas:
[&](auto x) constexpr {return x + 5;};constexpr if:
A compile-time 'if', only works on constexpr/compiletime constants:
template<typename T>
auto get_thing(T t) {
if constexpr(is_pointer_v<T>)
return *t;
else if constexpr(is_floating_point_v<T>)
return static_cast<int>(t);
else
return t;
}structured bindings:
An anonymous, transparent object!
// Allows decomposition of a std::tuple, struct, or other type!
struct Foo {int a; int b; double c; string d;};
std::tuple<int, double, float> get_tup();
Foo get_foo();
[i, d, f] = get_tup();
[f_a, f_b, f_c, f_d] = get_foo();
// All above names introduced into current scope!
// structured bindings can have c-v qualifications,
// as well as be a reference!C++17 Library changes:
Important library changes to C++17.
std::string_view // pointer/length pair that emulates a std::string
string_view s = some_string; // observation pointer!
<filesystem> // based on boost::filesystem, a ton of libraries for FS stuff
// <algorithm> extended to allow execution policies for parallelized algorithms.
// does a find, but allows it to be executed in parallel where sequence
// doesn't matter.
std::find(par_unseq, v.begin(), v.end(), what_to_find());
//std::optional, a type wrapper to contain a possibly-valid value, checked
// via optional::value()/optional::value_or()
std::optional<std::string> create(b) {
if (b) return "Godzilla";
return {}; // returns an 'empty' optional.
}
void use() {
std::cout << create(true).value_or("empty") << '\n';
if (create(false))
std::cout <<"Empty!\n";
}C++17 Library changes:
Important library changes to C++17, continued.
// std::variant is a type-safe union, internally contains a 'which'/value pair.
std::variant<int, float, int, double, string> v; // can contain any of those.
// accessed via std::get<integral> or std::get<type>, throws if type/int is wrong:
v = "str";
try { std::get<0>(v);} catch(std::bad_variant_access&){}
// can get which one by variant::index() (returns a size_t);
// std::any contains a single value of ANY type. Provides 'has_value' (returns bool)
// and 'type' (returns typeid) methods. any_cast used to get a value out.
Q/A
Erich.Keane@intel.com
Modern C++ features
By Erich Keane
Modern C++ features
- 991