cpp_17
C++17 features
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.
template
struct MyContainer {
T val;
MyContainer() : val{} {}
MyContainer(T val) : val{val} {}
// ...
};
MyContainer c1 {1}; // OK MyContainer
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:
template
struct my_integer_sequence {
// Implementation here ...
};
// Explicitly pass type `int` as template argument.
auto seq = std::integer_sequence();
// 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.
Fold1 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 ...), whereopis a fold-operator andeis an unexpanded parameter pack, are called unary folds. - An expression of the form
(e1 op ... op e2), whereopare fold-operators, is called a binary fold. Eithere1ore2is an unexpanded parameter pack, but not both.
template
bool logicalAnd(Args... args) {
// Binary folding.
return (true && ... && args);
}
bool b = true;
bool& b2 = b;
logicalAnd(b, b2, true); // == true
template
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.
auto x1 {1, 2, 3}; // error: not a single element
auto x2 = {1, 2, 3}; // x2 is std::initializer_list
auto x3 {3}; // x3 is int
auto x4 {3.0}; // x4 is double
constexpr lambda
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);
Lambda capture this by value
Capturing this in a lambda's environment was previously reference-only. An example of where this is problematic is asynchronous code using callbacks that require an object to be available, potentially past its lifetime. *this (C++17) will now make a copy of the current object, while this (C++11) continues to capture by reference.
struct MyObj {
int value {123};
auto getValueCopy() {
return [*this] { return value; };
}
auto getValueRef() {
return [this] { return value; };
}
};
MyObj mo;
auto valueCopy = mo.getValueCopy();
auto valueRef = mo.getValueRef();
mo.value = 321;
valueCopy(); // 123
valueRef(); // 321
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.
// 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
It can also be used to declare and define a static member variable, such that it does not need to be initialized in the source file.
struct S {
S() : id{count++} {}
~S() { count--; }
int id;
static inline int count{0}; // declare and initialize count to 0 within the class
};
Nested namespaces
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.
using Coordinate = std::pair;
Coordinate origin() {
return Coordinate{0, 0};
}
const auto [ x, y ] = origin();
x; // == 0
y; // == 0
std::unordered_map mapping {
{"a", 1},
{"b", 2},
{"c", 3}
};
// Destructure by reference.
for (const auto& [key, value] : mapping) {
// Do something with key and value
}
Selection statements with initializer
New versions of the if and switch statements which simplify common code patterns and help users keep scopes tight.
{
std::lock_guard lk(mx);
if (v.empty()) v.push_back(val);
}
// vs.
if (std::lock_guard 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());
}
constexpr if
Write code that is instantiated depending on a compile-time condition.
template
constexpr bool isIntegral() {
if constexpr (std::is_integral::value) {
return true;
} else {
return false;
}
}
static_assert(isIntegral() == true);
static_assert(isIntegral() == true);
static_assert(isIntegral() == false);
struct S {};
static_assert(isIntegral() == false);
UTF-8 character literals
A character literal that begins with u8 is a character literal of type char. The value of a UTF-8 character literal is equal to its ISO 10646 code point value.
char x = u8'x';
Direct list initialization of enums
Enums can now be initialized using braced syntax.
enum byte : unsigned char {};
byte b {0}; // OK
byte c {-1}; // ERROR
byte d = byte{1}; // OK
byte e = byte{256}; // ERROR
[[fallthrough]], [[nodiscard]], [[maybe_unused]] attributes
C++17 introduces three new attributes: [[fallthrough]], [[nodiscard]] and [[maybe_unused]].
[[fallthrough]]indicates to the compiler that falling through in a switch statement is intended behavior. This attribute may only be used in a switch statement, and must be placed before the next case/default label.
switch (n) {
case 1:
// ...
[[fallthrough]];
case 2:
// ...
break;
case 3:
// ...
[[fallthrough]];
default:
// ...
}
[[nodiscard]]issues a warning when either a function or class has this attribute and its return value is discarded.
[[nodiscard]] bool do_something() {
return is_success; // true for success, false for failure
}
do_something(); // warning: ignoring return value of 'bool do_something()',
// declared with attribute 'nodiscard'
// Only issues a warning when `error_info` is returned by value.
struct [[nodiscard]] error_info {
// ...
};
error_info do_something() {
error_info ei;
// ...
return ei;
}
do_something(); // warning: ignoring returned value of type 'error_info',
// declared with attribute 'nodiscard'
[[maybe_unused]]indicates to the compiler that a variable or parameter might be unused and is intended.
void my_callback(std::string msg, [[maybe_unused]] bool error) {
// Don't care if `msg` is an error message, just log it.
log(msg);
}
__has_include
__has_include (operand) operator may be used in #if and #elif expressions to check whether a header or source file (operand) is available for inclusion or not.
One use case of this would be using two libraries that work the same way, using the backup/experimental one if the preferred one is not found on the system.
#ifdef __has_include
# if __has_include()
# include
# define have_optional 1
# elif __has_include()
# include
# define have_optional 1
# define experimental_optional
# else
# define have_optional 0
# endif
#endif
It can also be used to include headers existing under different names or locations on various platforms, without knowing which platform the program is running on, OpenGL headers are a good example for this which are located in OpenGL\ directory on macOS and GL\ on other platforms.
#ifdef __has_include
# if __has_include()
# include
# include
# elif __has_include()
# include
# include
# else
# error No suitable OpenGL headers found.
# endif
#endif
Class template argument deduction
Class template argument deduction (CTAD) allows the compiler to deduce template arguments from constructor arguments.
std::vector v{ 1, 2, 3 }; // deduces std::vector
std::mutex mtx;
auto lck = std::lock_guard{ mtx }; // deduces to std::lock_guard
auto p = new std::pair{ 1.0, 2.0 }; // deduces to std::pair
For user-defined types, deduction guides can be used to guide the compiler how to deduce template arguments if applicable:
template
struct container {
container(T t) {}
template
container(Iter beg, Iter end);
};
// deduction guide
template
container(Iter b, Iter e) -> container::value_type>;
container a{ 7 }; // OK: deduces container
std::vector v{ 1.0, 2.0, 3.0 };
auto b = container{ v.begin(), v.end() }; // OK: deduces container
container c{ 5, 6 }; // ERROR: std::iterator_traits::value_type is not a type