mirror of
https://github.com/AnthonyCalandra/modern-cpp-features.git
synced 2025-12-17 18:14:36 +03:00
433 lines
13 KiB
Markdown
433 lines
13 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)
|
|
- [variadic templates](#variadic-templates)
|
|
- [relaxing constraints on constexpr functions](#relaxing-constraints-on-constexpr-functions)
|
|
|
|
## 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);
|
|
```
|
|
|
|
### 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);
|
|
```
|
|
|
|
### 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
|
|
TODO
|
|
|
|
## C++11 Language Features
|
|
TODO
|
|
|
|
## C++11 Library Features
|
|
TODO
|