mirror of
https://github.com/AnthonyCalandra/modern-cpp-features.git
synced 2025-12-17 18:14:36 +03:00
505 lines
16 KiB
Markdown
505 lines
16 KiB
Markdown
# C++11/14/17
|
|
|
|
## Overview
|
|
Many of these descriptions and examples come from their proposal papers, summarized in my own words.
|
|
|
|
C++17 includes the following new language features:
|
|
- [template argument deduction for class templates](#template-argument-deduction-for-class-templates)
|
|
- [declaring non-type template parameters with auto](#declaring-non-type-template-parameters-with-auto)
|
|
- [folding expressions](#folding-expressions)
|
|
- [new rules for auto deduction from braced-init-list](#new-rules-for-auto-deduction---from---braced---init---list)
|
|
- [constexpr lambda](#constexpr-lambda)
|
|
- [inline variables](#inline-variables)
|
|
- [nested namespaces](#nested-namespaces)
|
|
- [structured bindings](#structured-bindings)
|
|
- [selection statements with initializer](#selection-statements-with-initializer)
|
|
- [constexpr if](#constexpr-if)
|
|
|
|
C++17 includes the following new library features:
|
|
- [std::variant](#stdvariant)
|
|
- [std::optional](#stdoptional)
|
|
- [std::any](#stdany)
|
|
- [std::string_view](#stdstring_view)
|
|
- [std::invoke](#stdinvoke)
|
|
- [std::apply](#stdapply)
|
|
- [splicing for maps and sets](#splicing-for-maps-and-sets)
|
|
|
|
C++14 includes the following new language features:
|
|
- [binary literals](#binary-literals)
|
|
- [generic lambda expressions](#generic-lambda-expressions)
|
|
- [return type deduction](#return-type-deduction)
|
|
- [decltype(auto)](#decltypeauto)
|
|
- [relaxing constraints on constexpr functions](#relaxing-constraints-on-constexpr-functions)
|
|
|
|
C++14 includes the following new library features:
|
|
- [user-defined literals for standard library types](#user-defined-literals-for-standard-library-types)
|
|
- [compile-time integer sequences](#compile---time-integer-sequences)
|
|
|
|
C++11 includes the following new language features:
|
|
- [move semantics](#move-semantics)
|
|
- [variadic templates](#variadic-templates)
|
|
- [rvalue references](#rvalue-references)
|
|
|
|
C++11 includes the following new library features:
|
|
- [std::move](#stdmove)
|
|
|
|
## C++17 Language Features
|
|
|
|
### Template argument deduction for class templates
|
|
Automatic template argument deduction much like how it's done for functions, but now including class constructors.
|
|
```c++
|
|
template <typename T>
|
|
struct MyContainer {
|
|
T val;
|
|
MyContainer(T val) : val(val) {}
|
|
// ...
|
|
};
|
|
MyContainer c1{ 1 }; // OK MyContainer<int>
|
|
MyContainer c2; // OK MyContainer<>
|
|
```
|
|
|
|
### Declaring non-type template parameters with auto
|
|
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:
|
|
```c++
|
|
// 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`.
|
|
|
|
### Folding expressions
|
|
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 ...)`, where `op` is a fold-operator and `e` is an unexpanded parameter pack, are called _unary folds_.
|
|
* An expression of the form `(e1 op1 ... op2 e2)`, where `op1` and `op2` are fold-operators, is called a _binary fold_. Either `e1` or `e2` are unexpanded parameter packs, but not both.
|
|
```c++
|
|
template<typename... Args>
|
|
bool logicalAnd(Args... args) {
|
|
// Binary folding.
|
|
return (true && ... && args);
|
|
}
|
|
bool b = true;
|
|
bool& b2 = b;
|
|
logicalAnd(b, b2, true); // == true
|
|
```
|
|
```c++
|
|
template<typename... Args>
|
|
auto sum(Args... args) {
|
|
// Unary folding.
|
|
return (... + args);
|
|
}
|
|
sum(1.0, 2.0f, 3); // == 6.0
|
|
```
|
|
|
|
### New rules for auto deduction from braced-init-list
|
|
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`.
|
|
```c++
|
|
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
|
|
```
|
|
|
|
### constexpr lambda
|
|
Compile-time lambdas using `constexpr`.
|
|
```c++
|
|
auto identity = [] (int n) constexpr { return n; };
|
|
static_assert(identity(123) == 123);
|
|
```
|
|
```c++
|
|
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);
|
|
```
|
|
```c++
|
|
constexpr int addOne(int n) {
|
|
return [n] { return n + 1; }();
|
|
}
|
|
|
|
static_assert(addOne(1) == 2);
|
|
```
|
|
|
|
### Inline variables
|
|
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.
|
|
```c++
|
|
// 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
|
|
```
|
|
|
|
### Nested namespaces
|
|
Using the namespace resolution operator to create nested namespace definitions.
|
|
```c++
|
|
namespace A {
|
|
namespace B {
|
|
namespace C {
|
|
int i;
|
|
}
|
|
}
|
|
}
|
|
// vs.
|
|
namespace A::B::C {
|
|
int i;
|
|
}
|
|
```
|
|
|
|
### Structured bindings
|
|
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.
|
|
```c++
|
|
using Coordinate = std::pair<int, int>;
|
|
Coordinate origin() {
|
|
return Coordinate{0, 0};
|
|
}
|
|
|
|
const auto [ x, y ] = origin();
|
|
x; // == 0
|
|
y; // == 0
|
|
```
|
|
|
|
### Selection statements with initializer
|
|
New versions of the `if` and `switch` statements which simplify common code patterns and help users keep scopes tight.
|
|
```c++
|
|
{
|
|
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);
|
|
}
|
|
```
|
|
```c++
|
|
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());
|
|
}
|
|
```
|
|
|
|
### constexpr if
|
|
Write code that is instantiated depending on a compile-time condition.
|
|
```c++
|
|
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);
|
|
```
|
|
|
|
## C++17 Library Features
|
|
|
|
### std::variant
|
|
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).
|
|
```c++
|
|
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
|
|
```
|
|
|
|
### std::optional
|
|
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.
|
|
```c++
|
|
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)) {
|
|
// ...
|
|
}
|
|
```
|
|
|
|
### std::any
|
|
A type-safe container for single values of any type.
|
|
```c++
|
|
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
|
|
```
|
|
|
|
### std::string_view
|
|
A non-owning reference to a string. Useful for providing an abstraction on top of strings (e.g. for parsing).
|
|
```c++
|
|
// 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);
|
|
```
|
|
```c++
|
|
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"
|
|
```
|
|
|
|
### std::invoke
|
|
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.
|
|
```c++
|
|
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
|
|
```
|
|
|
|
### std::apply
|
|
Invoke a `Callable` object with a tuple of arguments.
|
|
```c++
|
|
auto add = [] (int x, int y) {
|
|
return x + y;
|
|
};
|
|
std::apply(add, std::make_tuple( 1, 2 )); // == 3
|
|
```
|
|
|
|
### Splicing for maps and sets
|
|
Moving nodes and merging containers without the overhead of expensive copies, moves, or heap allocations/deallocations.
|
|
|
|
Moving elements from one map to another:
|
|
```c++
|
|
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:
|
|
```c++
|
|
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:
|
|
```c++
|
|
auto elementFactory() {
|
|
std::set<...> s;
|
|
s.emplace(...);
|
|
return s.extract(s.begin());
|
|
}
|
|
s2.insert(elementFactory());
|
|
```
|
|
|
|
Changing the key of a map element:
|
|
```c++
|
|
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" } }
|
|
```
|
|
|
|
## C++14 Language Features
|
|
|
|
### Binary literals
|
|
Binary literals provide a convenient way to represent a base-2 number.
|
|
```c++
|
|
0b110 // == 6
|
|
0b11111111 // == 255
|
|
```
|
|
|
|
### Generic lambda expressions
|
|
C++14 now allows the `auto` type-specifier in the parameter list, enabling polymorphic lambdas.
|
|
```c++
|
|
auto identity = [](auto x) { return x; };
|
|
int three = identity(3); // == 3
|
|
std::string foo = identity("foo"); // == "foo"
|
|
```
|
|
|
|
### Return type deduction
|
|
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.
|
|
```c++
|
|
// Deduce return type as `int`.
|
|
auto f(int i) {
|
|
return i;
|
|
}
|
|
```
|
|
```c++
|
|
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`
|
|
```
|
|
|
|
### decltype(auto)
|
|
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.
|
|
```c++
|
|
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&&
|
|
```
|
|
```c++
|
|
// 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);
|
|
```
|
|
|
|
### Relaxing constraints on constexpr functions
|
|
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.
|
|
```c++
|
|
constexpr int factorial(int n) {
|
|
if (n <= 1) {
|
|
return 1;
|
|
} else {
|
|
return n * factorial(n - 1);
|
|
}
|
|
}
|
|
factorial(5); // == 120
|
|
```
|
|
|
|
## C++14 Library Features
|
|
|
|
### User-defined literals for standard library types
|
|
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.
|
|
```c++
|
|
using namespace std::chrono_literals;
|
|
auto day = 24h;
|
|
day.count(); // == 24
|
|
std::chrono::duration_cast<std::chrono::minutes>(day).count(); // == 1440
|
|
```
|
|
|
|
### Compile-time integer sequences
|
|
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 of `0, ..., N - 1` with type `T`.
|
|
* `std::index_sequence_for<T...>` - converts a template parameter pack into an integer sequence.
|
|
|
|
Convert an array into a tuple:
|
|
```c++
|
|
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());
|
|
}
|
|
```
|
|
|
|
## C++11 Language Features
|
|
|
|
### Move semantics
|
|
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`.
|
|
|
|
### Rvalue references
|
|
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:
|
|
```c++
|
|
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`.
|
|
|
|
### Variadic templates
|
|
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_.
|
|
```c++
|
|
template <typename... T>
|
|
struct arity {
|
|
constexpr static int value = sizeof...(T);
|
|
};
|
|
static_assert(arity<>::value == 0);
|
|
static_assert(arity<char, short, int>::value == 3);
|
|
```
|
|
|
|
## C++11 Library Features
|
|
|
|
### std::move
|
|
TODO
|
|
|
|
Definition of `std::move` (performing a move is nothing more than casting to an rvalue):
|
|
```c++
|
|
template <typename T>
|
|
typename remove_reference<T>::type&& move(T&& arg)
|
|
{
|
|
return static_cast<typename remove_reference<T>::type&&>(arg);
|
|
}
|
|
```
|