Many of these descriptions and examples come from various resources (see Acknowledgements section), summarized in my own words.
C 17 includes the following new language features:
- template argument deduction for class templates
- declaring non-type template parameters with auto
- folding expressions
- new rules for auto deduction from braced-init-list
- constexpr lambda
- inline variables
- nested namespaces
- structured bindings
- selection statements with initializer
- constexpr if
C 17 includes the following new library features:
- std::variant
- std::optional
- std::any
- std::string_view
- std::invoke
- std::apply
- splicing for maps and sets
C 14 includes the following new language features:
- binary literals
- generic lambda expressions
- return type deduction
- decltype(auto)
- relaxing constraints on constexpr functions
C 14 includes the following new library features:
C 11 includes the following new language features:
- move semantics
- variadic templates
- rvalue references
- initializer lists
- static assertions
- auto
- lambda expressions
- decltype
- template aliases
- nullptr
- strongly-typed enums
- attributes
- constexpr
- delegating constructors
- user-defined literals
- explicit virtual overrides
- default functions
- deleted functions
- range-based for loops
- special member functions for move semantics
C 11 includes the following new library features:
- std::move
- std::forward
- std::to_string
- type traits
- smart pointers
- std::chrono
- tuples
- std::tie
- std::array
- unordered containers
- memory model
Automatic template argument deduction much like how it's done for functions, but now including class constructors.
template <typename T = float>
struct MyContainer {
T val;
MyContainer() : val() {}
MyContainer(T val) : val(val) {}
// ...
};
MyContainer c1{ 1 }; // OK MyContainer<int>
MyContainer c2; // OK MyContainer<float>
Following the deduction rules of auto
, while respecting the non-type template parameter list of allowable types[*], template arguments can be deduced from the types of its arguments:
// Explicitly pass type `int` as template argument.
auto seq = std::integer_sequence<int, 0, 1, 2>();
// Type is deduced to be `int`.
auto seq2 = my_integer_sequence<0, 1, 2>();
* - For example, you cannot use a double
as a template parameter type, which also makes this an invalid deduction using auto
.
A fold expression performs a fold of a template parameter pack over a binary operator.
- An expression of the form
(... op e)
or(e op ...)
, whereop
is a fold-operator ande
is an unexpanded parameter pack, are called unary folds. - An expression of the form
(e1 op1 ... op2 e2)
, whereop1
andop2
are fold-operators, is called a binary fold. Eithere1
ore2
are unexpanded parameter packs, but not both.
template<typename... Args>
bool logicalAnd(Args... args) {
// Binary folding.
return (true && ... && args);
}
bool b = true;
bool& b2 = b;
logicalAnd(b, b2, true); // == true
template<typename... Args>
auto sum(Args... args) {
// Unary folding.
return (... args);
}
sum(1.0, 2.0f, 3); // == 6.0
Changes to auto
deduction when used with the uniform initialization syntax. Previously, auto x{ 3 };
deduces a std::initializer_list<int>
, which now deduces to int
.
auto x1{ 1, 2, 3 }; // error: not a single element
auto x2 = { 1, 2, 3 }; // decltype(x2) is std::initializer_list<int>
auto x3{ 3 }; // decltype(x3) is int
auto x4{ 3.0 }; // decltype(x4) is double
Compile-time lambdas using constexpr
.
auto identity = [] (int n) constexpr { return n; };
static_assert(identity(123) == 123);
constexpr auto add = [] (int x, int y) {
auto L = [=] { return x; };
auto R = [=] { return y; };
return [=] { return L() R(); };
};
static_assert(add(1, 2)() == 3);
constexpr int addOne(int n) {
return [n] { return n 1; }();
}
static_assert(addOne(1) == 2);
The inline specifier can be applied to variables as well as to functions. A variable declared inline has the same semantics as a function declared inline.
// Disassembly example using compiler explorer.
struct S { int x; };
inline S x1 = S{321}; // mov esi, dword ptr [x1]
// x1: .long 321
S x2 = S{123}; // mov eax, dword ptr [.L_ZZ4mainE2x2]
// mov dword ptr [rbp - 8], eax
// .L_ZZ4mainE2x2: .long 123
Using the namespace resolution operator to create nested namespace definitions.
namespace A {
namespace B {
namespace C {
int i;
}
}
}
// vs.
namespace A::B::C {
int i;
}
A proposal for de-structuring initialization, that would allow writing auto {x, y, z} = expr;
where the type of expr
was a tuple-like object, whose elements would be bound to the variables x
, y
, and z
(which this construct declares). Tuple-like objects include std::tuple
, std::pair
, std::array
, and aggregate structures.
using Coordinate = std::pair<int, int>;
Coordinate origin() {
return Coordinate{0, 0};
}
const auto [ x, y ] = origin();
x; // == 0
y; // == 0
New versions of the if
and switch
statements which simplify common code patterns and help users keep scopes tight.
{
std::lock_guard<std::mutex> lk(mx);
if (v.empty()) v.push_back(val);
}
// vs.
if (std::lock_guard<std::mutex> lk(mx); v.empty()) {
v.push_back(val);
}
Foo gadget(args);
switch (auto s = gadget.status()) {
case OK: gadget.zip(); break;
case Bad: throw BadFoo(s.message());
}
// vs.
switch (Foo gadget(args); auto s = gadget.status()) {
case OK: gadget.zip(); break;
case Bad: throw BadFoo(s.message());
}
Write code that is instantiated depending on a compile-time condition.
template <typename T>
constexpr bool isIntegral() {
if constexpr (std::is_integral<T>::value) {
return true;
} else {
return false;
}
}
static_assert(isIntegral<int>() == true);
static_assert(isIntegral<char>() == true);
static_assert(isIntegral<double>() == false);
struct S {};
static_assert(isIntegral<S>() == false);
The class template std::variant
represents a type-safe union
. An instance of std::variant
at any given time holds a value of one of its alternative types (it's also possible for it to be valueless).
std::variant<int, double> v{ 12 };
std::get<int>(v); // == 12
std::get<0>(v); // == 12
v = 12.0;
std::get<double>(v); // == 12.0
std::get<1>(v); // == 12.0
The class template std::optional
manages an optional contained value, i.e. a value that may or may not be present. A common use case for optional is the return value of a function that may fail.
std::optional<std::string> create(bool b) {
if (b) {
return "Godzilla";
} else {
return {};
}
}
create(false).value_or("empty"); // == "empty"
create(true).value(); // == "Godzilla"
// optional-returning factory functions are usable as conditions of while and if
if (auto str = create(true)) {
// ...
}
A type-safe container for single values of any type.
std::any x{ 5 };
x.has_value() // == true
std::any_cast<int>(x) // == 5
std::any_cast<int&>(x) = 10;
std::any_cast<int>(x) // == 10
A non-owning reference to a string. Useful for providing an abstraction on top of strings (e.g. for parsing).
// Regular strings.
std::string_view cppstr{ "foo" };
// Wide strings.
std::wstring_view wcstr_v{ L"baz" };
// Character arrays.
char array[3] = {'b', 'a', 'r'};
std::string_view array_v(array, sizeof array);
std::string str{ " trim me" };
std::string_view v{ str };
v.remove_prefix(std::min(v.find_first_not_of(" "), v.size()));
str; // == " trim me"
v; // == "trim me"
Invoke a Callable
object with parameters. Examples of Callable
objects are std::function
or std::bind
where an object can be called similarly to a regular function.
template <typename Callable>
class Proxy {
Callable c;
public:
Proxy(Callable c): c(c) {}
template <class... Args>
decltype(auto) operator()(Args&&... args) {
// ...
return std::invoke(c, std::forward<Args>(args)...);
}
};
auto add = [] (int x, int y) {
return x y;
};
Proxy<decltype(add)> p{ add };
p(1, 2); // == 3
Invoke a Callable
object with a tuple of arguments.
auto add = [] (int x, int y) {
return x y;
};
std::apply(add, std::make_tuple( 1, 2 )); // == 3
Moving nodes and merging containers without the overhead of expensive copies, moves, or heap allocations/deallocations.
Moving elements from one map to another:
std::map<int, string> src{ { 1, "one" }, { 2, "two" }, { 3, "buckle my shoe" } };
std::map<int, string> dst{ { 3, "three" } };
dst.insert(src.extract(src.find(1))); // Cheap remove and insert of { 1, "one" } from `src` to `dst`.
dst.insert(src.extract(2)); // Cheap remove and insert of { 2, "two" } from `src` to `dst`.
// dst == { { 1, "one" }, { 2, "two" }, { 3, "three" } };
Inserting an entire set:
std::set<int> src{1, 3, 5};
std::set<int> dst{2, 4, 5};
dst.merge(src);
// src == { 5 }
// dst == { 1, 2, 3, 4, 5 }
Inserting elements which outlive the container:
auto elementFactory() {
std::set<...> s;
s.emplace(...);
return s.extract(s.begin());
}
s2.insert(elementFactory());
Changing the key of a map element:
std::map<int, string> m{ { 1, "one" }, { 2, "two" }, { 3, "three" } };
auto e = m.extract(2);
e.key() = 4;
m.insert(std::move(e));
// m == { { 1, "one" }, { 3, "three" }, { 4, "two" } }
Binary literals provide a convenient way to represent a base-2 number.
It is possible to separate digits with '
.
0b110 // == 6
0b1111'1111 // == 255
C 14 now allows the auto
type-specifier in the parameter list, enabling polymorphic lambdas.
auto identity = [](auto x) { return x; };
int three = identity(3); // == 3
std::string foo = identity("foo"); // == "foo"
Using an auto
return type in C 14, the compiler will attempt to deduce the type for you. With lambdas, you can now deduce its return type using auto
, which makes returning a deduced reference or rvalue reference possible.
// Deduce return type as `int`.
auto f(int i) {
return i;
}
template <typename T>
auto& f(T& t) {
return t;
}
// Returns a reference to a deduced type.
auto g = [](auto& x) -> auto& { return f(x); };
int y = 123;
int& z = g(y); // reference to `y`
The decltype(auto)
type-specifier also deduces a type like auto
does. However, it deduces return types while keeping their references or "const-ness", while auto
will not.
const int x = 0;
auto x1 = x; // int
decltype(auto) x2 = x; // const int
int y = 0;
int& y1 = y;
auto y2 = y; // int
decltype(auto) y3 = y; // int&
int&& z = 0;
auto z1 = std::move(z); // int
decltype(auto) z2 = std::move(z); // int&&
// Note: Especially useful for generic code!
// Return type is `int`.
auto f(const int& i) {
return i;
}
// Return type is `const int&`.
decltype(auto) g(const int& i) {
return i;
}
int x = 123;
static_assert(std::is_same<const int&, decltype(f(x))>::value == 0);
static_assert(std::is_same<int, decltype(f(x))>::value == 1);
static_assert(std::is_same<const int&, decltype(g(x))>::value == 1);
In C 11, constexpr
function bodies could only contain a very limited set of syntax, including (but not limited to): typedef
s, using
s, and a single return
statement. In C 14, the set of allowable syntax expands greatly to include the most common syntax such as if
statements, multiple return
s, loops, etc.
constexpr int factorial(int n) {
if (n <= 1) {
return 1;
} else {
return n * factorial(n - 1);
}
}
factorial(5); // == 120
New user-defined literals for standard library types, including new built-in literals for chrono
and basic_string
. These can be constexpr
meaning they can be used at compile-time. Some uses for these literals include compile-time integer parsing, binary literals, and imaginary number literals.
using namespace std::chrono_literals;
auto day = 24h;
day.count(); // == 24
std::chrono::duration_cast<std::chrono::minutes>(day).count(); // == 1440
The class template std::integer_sequence
represents a compile-time sequence of integers. There are a few helpers built on top:
std::make_integer_sequence<T, N...>
- creates a sequence of0, ..., N - 1
with typeT
.std::index_sequence_for<T...>
- converts a template parameter pack into an integer sequence.
Convert an array into a tuple:
template<typename Array, std::size_t... I>
decltype(auto) a2t_impl(const Array& a, std::integer_sequence<std::size_t, I...>) {
return std::make_tuple(a[I]...);
}
template<typename T, std::size_t N, typename Indices = std::make_index_sequence<N>>
decltype(auto) a2t(const std::array<T, N>& a) {
return a2t_impl(a, Indices());
}
Move semantics is mostly about performance optimization: the ability to move an object without the expensive overhead of copying. The difference between a copy and a move is that a copy leaves the source unchanged, and a move will leave the source either unchanged or radically different -- depending on what the source is. For plain old data, a move is the same as a copy.
To move an object means to transfer ownership of some resource it manages to another object. You could think of this as changing pointers held by the source object to be moved, or now held, by the destination object; the resource remains in its location in memory. Such an inexpensive transfer of resources is extremely useful when the source is an rvalue
, where the potentially dangerous side-effect of changing the source after the move is redundant since the source is a temporary object that won't be accessible later.
Moves also make it possible to transfer objects such as std::unique_ptr
s, smart pointers that are designed to hold a pointer to a unique object, from one scope to another.
See the sections on: rvalue references, defining move special member functions, std::move
, std::forward
.
C 11 introduces a new reference termed the rvalue reference. An rvalue reference to A
is created with the syntax A&&
. This enables two major features: move semantics; and perfect forwarding, the ability to pass arguments while maintaining information about them as lvalues/rvalues in a generic way.
auto
type deduction with lvalues and rvalues:
int x = 0; // `x` is an lvalue of type `int`
int& xl = x; // `xl` is an lvalue of type `int&`
int&& xr = x; // compiler error -- `x` is an lvalue
int&& xr2 = 0; // `xr2` is an lvalue of type `int&&`
auto& al = x; // `al` is an lvalue of type `int&`
auto&& al2 = x; // `al2` is an lvalue of type `int&`
auto&& ar = 0; // `ar` is an lvalue of type `int&&`
See also: std::move
, std::forward
.
The ...
syntax creates a parameter pack or expands one. A template parameter pack is a template parameter that accepts zero or more template arguments (non-types, types, or templates). A template with at least one parameter pack is called a variadic template.
template <typename... T>
struct arity {
constexpr static int value = sizeof...(T);
};
static_assert(arity<>::value == 0);
static_assert(arity<char, short, int>::value == 3);
A lightweight array-like container of elements created using a "braced list" syntax. For example, { 1, 2, 3 }
creates a sequences of integers, that has type std::initializer_list<int>
. Useful as a replacement to passing a vector of objects to a function.
int sum(const std::initializer_list<int>& list) {
int total = 0;
for (auto& e : list) {
total = e;
}
return total;
}
auto list = { 1, 2, 3 };
f(list); // == 6
f({ 1, 2, 3 }); // == 6
f({}); // == 0
Assertions that are evaluated at compile-time.
constexpr int x = 0;
constexpr int y = 1;
static_assert(x == y, "x != y");
auto
-typed variables are deduced by the compiler according to the type of their initializer.
auto a = 3.14; // double
auto b = 1; // int
auto& c = b; // int&
auto d = { 0 }; // std::initializer_list<int>
auto&& e = 1; // int&&
auto&& f = b; // int&
auto g = new auto(123); // int*
const auto h = 1; // const int
auto i = 1, j = 2, k = 3; // int, int, int
auto l = 1, m = true, n = 1.61; // error -- `l` deduced to be int, `m` is bool
auto o; // error -- `o` requires initializer
Extremely useful for readability, especially for complicated types:
std::vector<int> v = ...;
std::vector<int>::const_iterator cit = v.cbegin();
// vs.
auto cit = v.cbegin();
Functions can also deduce the return type using auto
. In C 11, a return type must be specified either explicitly, or using decltype
like so:
template <typename X, typename Y>
auto add(X x, Y y) -> decltype(x y) {
return x y;
}
add(1, 2); // == 3
add(1, 2.0); // == 3.0
add(1.5, 1.5); // == 3.0
The trailing return type in the above example is the declared type (see section on decltype
) of the expression x y
. For example, if x
is an integer and y
is a double, decltype(x y)
is a double. Therefore, the above function will deduce the type depending on what type the expression x y
yields. Notice that the trailing return type has access to its parameters, and this
when appropriate.
A lambda
is an unnamed function object capable of capturing variables in scope. It features: a capture list; an optional set of parameters with an optional trailing return type; and a body. Examples of capture lists:
[]
- captures nothing.[=]
- capture local objects (local variables, parameters) in scope by value.[&]
- capture local objects (local variables, parameters) in scope by reference.[this]
- capturethis
pointer by value.[a, &b]
- capture objectsa
by value,b
by reference.
int x = 1;
auto getX = [=]{ return x; };
getX(); // == 1
auto addX = [=](int y) { return x y; };
addX(1); // == 2
auto getXRef = [&]() -> int& { return x; };
getXRef(); // int& to `x`
decltype
is an operator which returns the declared type of an expression passed to it. Examples of decltype
:
int a = 1; // `a` is declared as type `int`
decltype(x) b = a; // `decltype(x)` is `int`
const int& c = a; // `c` is declared as type `const int&`
decltype(c) d = a; // `decltype(c)` is `const int&`
decltype(123) e = 123; // `decltype(123)` is `int`
int&& f = 1; // `f` is declared as type `int&&`
decltype(f) g = 1; // `decltype(f) is `int&&`
decltype((a)) h = x; // `decltype((a))` is int&
template <typename X, typename Y>
auto add(X x, Y y) -> decltype(x y) {
return x y;
}
add(1, 2.0); // `decltype(x y)` => `decltype(3.0)` => `double`
Semantically similar to using a typedef
however, template aliases with using
are easier to read and are compatible with templates.
template <typename T>
using Vec = std::vector<T>;
Vec<int> v{}; // std::vector<int>
using String = std::string;
String s{"foo"};
C 11 introduces a new null pointer type designed to replace C's NULL
macro. nullptr
itself is of type std::nullptr_t
and can be implicitly converted into pointer types, and unlike NULL
, not convertible to integral types except bool
.
void foo(int);
void foo(char*);
foo(NULL); // error -- ambiguous
foo(nullptr); // calls foo(char*)
Type-safe enums that solve a variety of problems with C-style enums including: implicit conversions, inability to specify the underlying type, scope pollution.
// Specifying underlying type as `unsigned int`
enum class Color : unsigned int { Red = 0xff0000, Green = 0xff00, Blue = 0xff };
// `Red`/`Green` in `Alert` don't conflict with `Color`
enum class Alert : bool { Red, Green };
Color c = Color::Red;
Attributes provide a universal syntax over __attribute__(...)
, __declspec
, etc.
// `noreturn` attribute indicates `f` doesn't return.
[[ noreturn ]] void f() {
throw "error";
}
Constant expressions are expressions evaluated by the compiler at compile-time. Only non-complex computations can be carried out in a constant expression. Use the constexpr
specifier to indicate the variable, function, etc. is a constant expression.
constexpr int square(int x) {
return x * x;
}
int square2(int x) {
return x * x;
}
int a = square(2); // mov DWORD PTR [rbp-4], 4
int b = square2(2); // mov edi, 2
// call square2(int)
// mov DWORD PTR [rbp-8], eax
constexpr
values are those that the compiler can evaluate at compile-time:
const int x = 123;
constexpr const int& y = x; // error -- constexpr variable `y` must be initialized by a constant expression
Constant expressions with classes:
struct Complex {
constexpr Complex(double r, double i) : re(r), im(i) { }
constexpr double real() { return re; }
constexpr double imag() { return im; }
private:
double re;
double im;
};
constexpr Complex I(0, 1);
Constructors can now call other constructors in the same class using an initializer list.
struct Foo {
int foo;
Foo(int foo) : foo(foo) {}
Foo() : Foo(0) {}
};
Foo foo{};
foo.foo; // == 0
User-defined literals allow you to extend the language and add your own syntax. To create a literal, define a T operator "" X(...) { ... }
function that returns a type T
, with a name X
. Note that the name of this function defines the name of the literal. Any literal names not starting with an underscore are reserved and won't be invoked. There are rules on what parameters a user-defined literal function should accept, according to what type the literal is called on.
Converting Celsius to Fahrenheit:
// `unsigned long long` parameter required for integer literal.
long long operator "" _celsius(unsigned long long tempCelsius) {
return std::llround(tempCelsius * 1.8 32);
}
24_celsius; // == 75
String to integer conversion:
// `const char*` and `std::size_t` required as parameters.
int operator "" _int(const char* str, std::size_t) {
return std::stoi(str);
}
"123"_int; // == 123, with type `int`
Specifies that a virtual function overrides another virtual function. If the virtual function does not override a parent's virtual function, throws a compiler error.
struct A {
virtual void foo();
void bar();
};
struct B : A {
void foo() override; // correct -- B::foo overrides A::foo
void bar() override; // error -- A::bar is not virtual
void baz() override; // error -- B::baz does not override A::baz
};
A more elegant, efficient way to provide a default implementation of a function, such as a constructor.
struct A {
A() = default;
A(int x) : x(x) {}
int x{ 1 };
};
A a{}; // a.x == 1
A a2{ 123 }; // a.x == 123
With inheritance:
struct B {
B() : x(1);
int x;
};
struct C : B {
// Calls B::B
C() = default;
};
C c{}; // c.x == 1
A more elegant, efficient way to provide a deleted implementation of a function. Useful for preventing copies on objects.
class A {
int x;
public:
A(int x) : x(x) {};
A(const A&) = delete;
A& operator=(const A&) = delete;
};
A x{ 123 };
A y = x; // error -- call to deleted copy constructor
y = x; // error -- operator= deleted
Syntactic sugar for iterating over a container's elements.
std::array<int, 5> a{ 1, 2, 3, 4, 5 };
for (int& x : a) x *= 2;
// a == { 2, 4, 6, 8, 10 }
Note the difference when using int
as opposed to int&
:
std::array<int, 5> a{ 1, 2, 3, 4, 5 };
for (int x : a) x *= 2;
// a == { 1, 2, 3, 4, 5 }
The copy constructor and copy assignment operator are called when copies are made, and with C 11's introduction of move semantics, there is now a move constructor and move assignment operator for moves.
struct A {
std::string s;
A() : s("test") {}
A(const A& o) : s(o.s) {}
A(A&& o) : s(std::move(o.s)) {}
A& operator=(A&& o) {
s = std::move(o.s);
return *this;
}
};
A f(A a) {
return a;
}
A a1 = f(A{}); // move-constructed from rvalue temporary
A a2 = std::move(a1); // move-constructed using std::move
A a3 = A{};
a2 = std::move(a3); // move-assignment using std::move
a1 = f(A{}); // move-assignment from rvalue temporary
std::move
indicates that the object passed to it may be moved, or in other words, moved from one object to another without a copy. The object passed in should not be used after the move in certain situations.
A definition of std::move
(performing a move is nothing more than casting to an rvalue):
template <typename T>
typename remove_reference<T>::type&& move(T&& arg) {
return static_cast<typename remove_reference<T>::type&&>(arg);
}
Transferring std::unique_ptr
s:
std::unique_ptr<int> p1{ new int };
std::unique_ptr<int> p2 = p1; // error -- cannot copy unique pointers
std::unique_ptr<int> p3 = std::move(p1); // move `p1` into `p2`
// now unsafe to dereference object held by `p1`
Returns the arguments passed to it as-is, either as an lvalue or rvalue references, and includes cv-qualification. Useful for generic code that need a reference (either lvalue or rvalue) when appropriate, e.g factories. Forwarding gets its power from template argument deduction:
T& &
becomesT&
T& &&
becomesT&
T&& &
becomesT&
T&& &&
becomesT&&
A definition of std::forward
:
template <typename T>
T&& forward(typename remove_reference<T>::type& arg) {
return static_cast<T&&>(arg);
}
An example of a function wrapper
which just forwards other A
objects to a new A
object's copy or move constructor:
struct A {
A() = default;
A(const A& o) { std::cout << "copied" << std::endl; }
A(A&& o) { std::cout << "moved" << std::endl; }
};
template <typename T>
A wrapper(T&& arg) {
return A{ std::forward<T>(arg) };
}
wrapper(A{}); // moved
A a{};
wrapper(a); // copied
wrapper(std::move(a)); // moved
Converts a numeric argument to a std::string
.
std::to_string(1.2); // == "1.2"
std::to_string(123); // == "123"
Type traits defines a compile-time template-based interface to query or modify the properties of types.
static_assert(std::is_integral<int>::value == 1);
static_assert(std::is_same<int, int>::value == 1);
static_assert(std::is_same<std::conditional<true, int, double>::type, int>::value == 1);
C 11 introduces new smart(er) pointers: std::unique_ptr
, std::shared_ptr
, std::weak_ptr
. std::auto_ptr
now becomes deprecated and then eventually removed in C 17. std::unique_ptr
is a non-copyable, movable smart pointer that properly manages arrays and STL containers.
std::unique_ptr<Foo> p1(new Foo); // `p1` owns `Foo`
if (p1) p1->bar();
{
std::unique_ptr<Foo> p2(std::move(p1)); // Now `p2` owns `Foo`
f(*p2);
p1 = std::move(p2); // Ownership returns to `p1` -- `p2` gets destroyed
}
if (p1) p1->bar();
// `Foo` instance is destroyed when `p1` goes out of scope
The chrono library contains a set of utility functions and types that deal with durations, clocks, and time points. One use case of this library is benchmarking code:
std::chrono::time_point<std::chrono::system_clock> start, end;
start = std::chrono::system_clock::now();
// Some computations...
end = std::chrono::system_clock::now();
std::chrono::duration<double> elapsed_seconds = end-start;
elapsed_seconds.count(); // t number of seconds, represented as a `double`
Tuples are a fixed-size collection of heterogeneous values. Access the elements of a std::tuple
by unpacking using std::tie
(see section), or using std::get
.
// `playerProfile` has type `std::tuple<int, std::string, std::string>`.
auto playerProfile = std::make_tuple(51, "Frans Nielsen", "NYI");
std::get<0>(playerProfile); // 51
std::get<1>(playerProfile); // "Frans Nielsen"
std::get<2>(playerProfile); // "NYI"
Creates a tuple of lvalue references. Useful for unpacking std::pair
and std::tuple
objects. Use std::ignore
as a placeholder for ignored values. In C 17, structured bindings should be used instead.
// With tuples...
std::string playerName;
std::tie(std::ignore, playerName, std::ignore) = std::make_tuple(91, "John Tavares", "NYI");
// With pairs...
std::string yes, no;
std::tie(yes, no) = std::make_pair("yes", "no");
std::array
is a container built on top of a C-style array. Supports common container operations such as sorting.
std::array<int, 3> a = {2, 1, 3};
std::sort(a.begin(), a.end()); // a == { 1, 2, 3 }
for (int& x : a) x *= 2; // a == { 2, 4, 6 }
These containers maintain average constant-time complexity for search, insert, and remove operations. In order to achieve constant-time complexity, sacrifices order for speed by hashing elements into buckets. There are four unordered containers:
unordered_set
unordered_multiset
unordered_map
unordered_multimap
C 11 introduces a memory model for C , which means library support for threading and atomic operations. Some of these operations include (but aren't limited to) atomic loads/stores, compare-and-swap, atomic flags, promises, futures, locks, and condition variables.
- cppreference - especially useful for finding examples and documentation of new library features.
- C Rvalue References Explained - a great introduction I used to understand rvalue references, perfect forwarding, and move semantics.
- clang and gcc's standards support pages. Also included here are the proposals for language/library features that I used to help find a description of, what it's meant to fix, and some examples.
- Compiler explorer
- Scott Meyers' Effective Modern C - highly recommended book!
- Jason Turner's C Weekly - nice collection of C -related videos.
- What can I do with a moved-from object?
- What are some uses of decltype(auto)?
- And many more SO posts I'm forgetting...
Anthony Calandra
MIT