The ISO Committee accepted and published the C++17 Standard in December 2017. In this mega-long article, I’ve built (with your help!) a list of all major features of the new standard.

Please have a look and see what we get!


Updated: This post was updated on 10th October 2022.

If you have code examples, better explanations or any ideas, let me know! I am happy to update the current post so that it has some real value for others.

The plan is to have a list of features with some basic explanation, little example (if possible) and some additional resources, plus a note about availability in compilers. Probably, most of the features might require separate articles or even whole chapters in books, so the list here will be only a jump start.

See this GitHub repo: github/fenbf/cpp17features. Add a pull request to update the content.

The feature list comes from the following resources:

And one of the most important resource: N4659, 2017-03-21, Draft, Standard for Programming Language C++ - from

Plus, there’s an official list of changes: P0636r0: Changes between C++14 and C++17 DIS

Also, you can grab my list of concise descriptions of all of the C++17 - It’s a one-page reference card:

I also have a more detailed series:

  1. Fixes and deprecation
  2. Language clarification
  3. Templates
  4. Attributes
  5. Simplification
  6. Library changes - Filesystem
  7. Library changes - Parallel STL
  8. Library changes - Utils
  9. Wrap up, Bonus - with a free ebook! :)

And another cool article:

Resources about C++17 STL:

Language Features  

New auto rules for direct-list-initialization  


GCC: 5.0 Clang: 3.8 MSVC: 14.0

Fixes some cases with auto type deduction. The full background can be found in Auto and braced-init-lists, by Ville Voutilainen.

It fixes the problem of deducing std::initializer_list like:

auto x = foo(); // copy-initialization
auto x{foo}; // direct-initialization, initializes an initializer_list
int x = foo(); // copy-initialization
int x{foo}; // direct-initialization

And for the direct initialization, new rules are:

  • For a braced-init-list with only a single element, auto deduction will deduce from that entry;
  • For a braced-init-list with more than one element, auto deduction will be ill-formed.

Basically, auto x { 1 }; will be now deduced as int, but before it was an initializer list.

static_assert with no message  


GCC: 6.0 Clang: 2.5 MSVC: 15.0 preview 5

Self-explanatory. It allows having the condition without passing the message, version with the message will also be available. It will be compatible with other asserts like BOOST_STATIC_ASSERT (that didn’t take any message from the start).

constexpr int identity(int x) { return x; }
static_assert(identity(10) == 10, "expected the same value"); // since C++11
static_assert(identity(10) == 10); // no message, since C++17

Run at @compiler Explorer

typename in a template template parameter  


GCC: 5.0 Clang: 3.5 MSVC: 14.0

Allows you to use typename instead of class when declaring a template template parameter. Normal type parameters can use them interchangeably, but template template parameters were restricted to class, so this change unifies these forms somewhat.

template <template <typename...> typename Container>
//            used to be invalid ^^^^^^^^
struct foo;

foo<std::vector> my_foo;

Removing trigraphs  


GCC: 5.1 Clang: 3.5 MSVC: Yes

Removes ??=, ??(, ??>, …

Makes the implementation a bit simpler, see MSDN Trigraphs

Nested namespace definition  


GCC: 6.0 Clang: 3.6 MSVC: 14.3

Allows writing:

namespace A::B::C {
   // ...

Rather than:

namespace A {
    namespace B {
        namespace C {
            // ...

Attributes for namespaces and enumerators  


GCC: 4.9 (namespaces)/ 6 (enums) Clang: 3.4 MSVC: 14.0

Permits attributes on enumerators and namespaces. More details in N4196.

enum E {
  foobar = 0,
  foobat [[deprecated]] = foobar

E e = foobat; // Emits warning

namespace [[deprecated]] old_stuff{
    void legacy();

old_stuff::legacy(); // Emits warning

u8 character literals  


GCC: 6.0 Clang: 3.6 MSVC: 14.0

UTF-8 character literal, e.g. u8'a'. Such literal has type char and the value equal to ISO 10646 code point value of c-char, provided that the code point value is representable with a single UTF-8 code unit. If c-char is not in Basic Latin or C0 Controls Unicode block, the program is ill-formed.

The compiler will report errors if a character cannot fit inside u8 ASCII range.


Allow constant evaluation for all non-type template arguments  


GCC: 6.0 Clang: 3.6 MSVC: VS 2017 15.5

Remove syntactic restrictions for pointers, references, and pointers to members that appear as non-type template parameters:

For instance:

template<int *p> struct A {};
int n;
A<&n> a; // ok

constexpr int *p() { return &n; }
A<p()> b; // error before C++17

Fold Expressions  


GCC: 6.0 Clang: 3.6 MSVC: VS 2017 15.5

More background here in P0036

Allows writing compact code with variadic templates without using explicit recursion.


template<typename... Args>
auto SumWithOne(Args... args){
    return (1 + ... + args);


Unary fold expressions and empty parameter packs  


GCC: 6.0 Clang: 3.9 MSVC: VS 2017 15.5

If the parameter pack is empty then the value of the fold is:

Operator Value
&& true
|| false
, void()

For any operator not listed above, an unary fold expression with an empty parameter pack is ill-formed.

Remove Deprecated Use of the register Keyword  


GCC: 7.0 Clang: 3.8 MSVC: 15.3

The register keyword was deprecated in the 2011 C++ standard. C++17 tries to clear the standard, so the keyword is now removed. This keyword is reserved now and might be repurposed in future revisions.

Remove Deprecated operator++(bool)  


GCC: 7.0 Clang: 3.8 MSVC: 15.3

The ++operator for bool was deprecated in the original 1998 C++ standard, and it is past time to remove it formally.

Removing Deprecated Exception Specifications from C++17  


GCC: 7.0 Clang: 4.0 MSVC: VS 2017 15.0

Dynamic exception specifications were deprecated in C++11. This paper formally proposes removing the feature from C++17, while retaining the (still) deprecated throw() specification strictly as an alias for noexcept(true).

Make exception specifications part of the type system  


GCC: 7.0 Clang: 4.0 MSVC: VS 2017 15.5

Previously exception specifications for a function didn’t belong to the type of the function, but it will be part of it.

We’ll get an error in the case:

void (*p)();
void (**pp)() noexcept = &p;   // error: cannot convert to pointer to noexcept function

struct S { typedef void (*p)(); operator p(); };
void (*q)() noexcept = S();   // error: cannot convert to pointer to noexcept function

Aggregate initialization of classes with base classes  


GCC: 7.0 Clang: 3.9 MSVC: VS 2017 15.7

If a class was derived from some other type you couldn’t use aggregate initialization. But now the restriction is removed.

struct base { int a1, a2; };
struct derived : base { int b1; };

derived d1{{1, 2}, 3};      // full explicit initialization
derived d1{{}, 1};          // the base is value initialized

To sum up: from the standard:

An aggregate is an array or a class with:
* no user-provided constructors (including those inherited from a base class),
* no private or protected non-static data members (Clause 11),
* no base classes (Clause 10) and // removed now!
* no virtual functions (10.3), and
* no virtual, private or protected base classes (10.1).

See more in Five tricky topics for data members in C++20 - C++ Stories - “Changing status of aggregates”

Lambda capture of *this  


GCC: 7.0 Clang: 3.9 MSVC: 15.3

this pointer is implicitly captured by lambdas inside member functions (if you use a default capture, like [&] or [=]). Member variables are always accessed by this pointer.


struct S {
   int x ;
   void f() {
      // The following lambda captures are currently identical
      auto a = [&]() { x = 42 ; } // OK: transformed to (*this).x
      auto b = [=]() { x = 43 ; } // OK: transformed to (*this).x
      assert( x == 42 );
      assert( x == 43 );

Now you can use *this when declaring a lambda, for example auto b = [=, *this]() { x = 43 ; }. That way this is captured by value. Note that the form [&,this] is redundant but accepted for compatibility with ISO C++14.

Capturing by value might be especially important for async invocation, paraller processing.

See more at C++ Lambdas, Threads, std::async and Parallel Algorithms - C++ Stories

Using attribute namespaces without repetition  


GCC: 7.0 Clang: 3.9 MSVC: 15.3

Other name for this feature was “Using non-standard attributes” in P0028R3 and PDF: P0028R2 (rationale, examples).

Simplifies the case where you want to use multiple attributes, like:

void f() {
    [[rpr::kernel, rpr::target(cpu,gpu)]] // repetition

Proposed change:

void f() {
    [[using rpr: kernel, target(cpu,gpu)]]

That simplification might help when building tools that automatically translate annotated such code into a different programming models.

Dynamic memory allocation for over-aligned data  


GCC: 7.0 Clang: 4.0 MSVC: VS 2017 15.5

In the following example:

class alignas(16) float4 {
    float f[4];
float4 *p = new float4[1000];

C++11/14 did not specify any mechanism by which over-aligned data can be dynamically allocated correctly (i.e. respecting the alignment of the data). In the example above, not only is an implementation of C++ not required to allocate properly-aligned memory for the array, for practical purposes it is very nearly required to do the allocation incorrectly.

C++17 fixes that hole by introducing additional memory allocation functions that use align parameter:

void* operator new(std::size_t, std::align_val_t);
void* operator new[](std::size_t, std::align_val_t);
void operator delete(void*, std::align_val_t);
void operator delete[](void*, std::align_val_t);
void operator delete(void*, std::size_t, std::align_val_t);
void operator delete[](void*, std::size_t, std::align_val_t);

See more in New new() - The C++17’s Alignment Parameter for Operator new() - C++ Stories.

__has_include in preprocessor conditionals  


GCC: 5.0 Clang: yes MSVC: 15.3

This feature allows a C++ program to directly, reliably and portably determine whether or not a library header is available for inclusion.

Example: This demonstrates a way to use a library optional facility only if it is available.

#if __has_include(<optional>)
#  include <optional>
#  define have_optional 1
#elif __has_include(<experimental/optional>)
#  include <experimental/optional>
#  define have_optional 1
#  define experimental_optional 1
#  define have_optional 0

Template argument deduction for class templates  


GCC: 7.0 Clang: 5 MSVC: VS 2017 15.7

Before C++17, template deduction worked for functions but not for classes. For instance, the following code was legal:

void f(std::pair<int, char>);

f(std::make_pair(42, 'z'));

because std::make_pair is a template function (so we can perform template deduction). But the following wasn’t:

void f(std::pair<int, char>);

f(std::pair(42, 'z'));

Although it is semantically equivalent. This was not legal because std::pair is a template class, and template classes could not apply type deduction in their initialization.

So before C++17 one has to write out the types explicitly, even though this does not add any new information:

void f(std::pair<int, char>);

f(std::pair<int, char>(42, 'z'));

This is fixed in C++17 where template class constructors can deduce type parameters. The syntax for constructing such template classes is therefore consistent with the syntax for constructing non-template classes.

See more in:

Non-type template parameters with auto type  


GCC: 7.0 Clang: 4.0 MSVC: VS 2017 15.7

Automatically deduce type on non-type template parameters.

template <auto value> void f() { }
f<10>();               // deduces int

Trip report: Summer ISO C++ standards meeting (Oulu) | Sutter’s Mill

Guaranteed copy elision  


Copy elision for temporary objects, not for Named RVO.

GCC: 7.0 Clang: 4.0 MSVC: VS 2017 15.6
// based on P0135R0
struct NonMoveable 
  // no copy or move constructor:
  NonMoveable(const NonMoveable&) = delete;
  NonMoveable(NonMoveable&&) = delete;

  std::array<int, 1024> arr;

NonMoveable make() 
  return NonMoveable(42);

// construct the object:
auto largeNonMovableObj = make();


New specification for inheriting constructors (DR1941 et al)  


GCC: 7.0 Clang: 3.9 MSVC: VS 2017 15.7

More description and reasoning in P0136R0. Some excerpts below:

An inheriting constructor does not act like any other form of using-declaration. All other using-declarations make some set of declarations visible to name lookup in another context, but an inheriting constructor declaration declares a new constructor that merely delegates to the original.

This feature changes inheriting constructor declaration from declaring a set of new constructors, to making a set of base class constructors visible in a derived class as if they were derived class constructors. (When such a constructor is used, the additional derived class subobjects will also be implicitly constructed as if by a defaulted default constructor). Put another way: make inheriting a constructor act just like inheriting any other base class member, to the extent possible.

This change does affect the meaning and validity of some programs, but these changes improve the consistency and comprehensibility of C++.

// Hiding works the same as for other member
// using-declarations in the presence of default arguments
struct A {
  A(int a, int b = 0);
  void f(int a, int b = 0);
struct B : A {
  B(int a);      using A::A;
  void f(int a); using A::f;
struct C : A {
  C(int a, int b = 0);      using A::A;
  void f(int a, int b = 0); using A::f;

B b(0); // was ok, now ambiguous
b.f(0); // ambiguous (unchanged)

C c(0); // was ambiguous, now ok
c.f(0); // ok (unchanged)

// Inheriting constructor parameters are no longer copied
struct A { A(const A&) = delete; A(int); };
struct B { B(A); void f(A); };
struct C : B { using B::B; using B::f; };
C c({0}); // was ill-formed, now ok (no copy made)
c.f({0}); // ok (unchanged)

Direct-list-initialization of enumerations  


GCC: 7.0 Clang: 3.9 MSVC: 15.3

Allows the initialization of an enum class with a fixed underlying type:

enum class Handle : uint32_t { Invalid = 0 };
Handle h { 42 }; // OK

Allows creating strong types that are easy to use…

Stricter expression evaluation order  


GCC: 7.0 Clang: 4.0 MSVC: VS 2017 15.7

In a nutshell, given an expression such as f(a, b, c), the order in which the sub-expressions f, a, b, c (which are of arbitrary shapes) are evaluated is left unspecified by the standard.

// unspecified behaviour below!
f(i++, i);

v[i] = i++;

std::map<int, int> m;
m[0] = m.size(); // {{0, 0}} or {{0, 1}} ?

Summary of changes:

  • Postfix expressions are evaluated from left to right. This includes functions calls and member selection expressions.
  • Assignment expressions are evaluated from right to left. This includes compound assignments.
  • Operands to shift operators are evaluated from left to right.

See more in: Stricter Expression Evaluation Order in C++17 - C++ Stories


constexpr lambda expressions  


GCC: 7.0 Clang: 5 MSVC: 15.3

constexpr can be used in the context of lambdas.

constexpr auto ID = [] (int n)  { return n; };
constexpr int I = ID(3);
static_assert(I == 3);

constexpr int AddEleven(int n) {
  // Initialization of the 'data member' for n can
  // occur within a constant expression since 'n' is
  // of literal type.
  return [n] { return n + 11; }();
static_assert(AddEleven(5) == 16);


Different begin and end types in range-based for  


GCC: 6.0 Clang: 3.6 MSVC: 15.0 Preview 5

Changing the definition of range based for from:

   auto && __range = for-range-initializer;
   for ( auto __begin = begin-expr,
              __end = end-expr;
              __begin != __end;
              ++__begin ) {
        for-range-declaration = *__begin;


  auto && __range = for-range-initializer;
  auto __begin = begin-expr;
  auto __end = end-expr;
  for ( ; __begin != __end; ++__begin ) {
    for-range-declaration = *__begin;

Types of __begin and __end might be different; only the comparison operator is required. This little change allows Range TS users a better experience.

[[fallthrough]] attribute  


GCC: 7.0 Clang: 3.9 MSVC: 15.0 Preview 4

Indicates that a fallthrough in a switch statement is intentional and a warning should not be issued for it. More details in P0068R0.

switch (c) {
case 'a':
    f(); // Warning emitted, fallthrough is perhaps a programmer error
case 'b':
[[fallthrough]]; // Warning suppressed, fallthrough is intentional
case 'c':

See more in C++17 in details: Attributes - C++ Stories

[[nodiscard]] attribute  


GCC: 7.0 Clang: 3.9 MSVC: 15.3

[[nodiscard]] is used to stress that the return value of a function is not to be discarded, on pain of a compiler warning. More details in P0068R0.

[[nodiscard]] int foo();
void bar() {
    foo(); // Warning emitted, return value of a nodiscard function is discarded

This attribute can also be applied to types in order to mark all functions which return that type as [[nodiscard]]:

[[nodiscard]] struct DoNotThrowMeAway{};
DoNotThrowMeAway i_promise();
void oops() {
    i_promise(); // Warning emitted, return value of a nodiscard function is discarded


[[maybe_unused]] attribute  


GCC: 7.0 Clang: 3.9 MSVC: 15.3

Suppresses compiler warnings about unused entities when they are declared with [[maybe_unused]]. More details in P0068R0.

                 static void impl1() { ... } // Compilers may warn about this
[[maybe_unused]] static void impl2() { ... } // Warning suppressed

void foo() {
                      int x = 42; // Compilers may warn about this
     [[maybe_unused]] int y = 42; // Warning suppressed

A 3 min video about maybe_unused in Jason Turner’s C++ Weekly

Ignore unknown attributes  


GCC: Yes Clang: 3.9 MSVC: VS 2017 15.3

Clarifies that implementations should ignore any attribute namespaces which they do not support, as this used to be unspecified. More details in P0283R1.

//compilers which don't support MyCompilerSpecificNamespace will ignore this attribute
void foo();

Pack expansions in using-declarations  


GCC: 7.0 Clang: 4.0 MSVC: VS 2017 15.7

Allows you to inject names with using-declarations from all types in a parameter pack.

In order to expose operator() from all base classes in a variadic template, we used to have to resort to recursion:

template <typename T, typename... Ts>
struct Overloader : T, Overloader<Ts...> {
    using T::operator();
    using Overloader<Ts...>::operator();
    // […]

template <typename T> struct Overloader<T> : T {
    using T::operator();

Now we can simply expand the parameter pack in the using-declaration:

template <typename... Ts>
struct Overloader : Ts... {
    using Ts::operator()...;
    // […]

See more in 2 Lines Of Code and 3 C++17 Features - The overload Pattern - C++ Stories


Structured Binding Declarations  

P0615R0: Renaming for structured bindings

GCC: 7.0 Clang: 4.0 MSVC: 15.3

Helps when using tuples as a return type. It will automatically create variables and tie them. More details in P0144R0.

The name “Decomposition Declaration” was also used, but finally the standard agrees to use “Structured Binding Declarations” (section 11.5)

For example:

int a = 0;
double b = 0.0;
long c = 0;
std::tie(a, b, c) = tuple; // a, b, c need to be declared first

Now we can write:

auto [ a, b, c ] = tuple;

Such expressions also work on structs, pairs, and arrays.


Hexadecimal floating-point literals  


GCC: 3.0 Clang: Yes MSVC: VS 2017 15.3t

Allows expressing some special floating point values, for example, the smallest normal IEEE-754 single precision value is readily written as 0x1.0p-126.

init-statements for if and switch  


GCC: 7.0 Clang: 3.9 MSVC: 15.3

New versions of the if and switch statements for C++:

if (init; condition) and switch (init; condition).

This should simplify the code. For example, previously you had to write:

    auto val = GetValue();
    if (condition(val))
        // on success
        // on false...

Look, that val has a separate scope, without it it will ‘leak’.

Now you can write:

if (auto val = GetValue(); condition(val))
    // on success
    // on false...

val is visible only inside the if and else statements, so it doesn’t ‘leak’. condition might be any condition, not only if val is true/false.


Inline variables  


GCC: 7.0 Clang: 3.9 MSVC: VS 2017 15.5

Previously only methods/functions could be specified as inline, now you can do the same with variables, inside a header file.

A variable declared inline has the same semantics as a function declared inline: it can be defined, identically, in multiple translation units, must be defined in every translation unit in which it is used, and the behavior of the program is as if there is exactly one variable.

struct MyClass
    static const int sValue;

inline int const MyClass::sValue = 777;

Or even:

struct MyClass
    inline static const int sValue = 777;


DR: Matching of template template-arguments excludes compatible templates  


GCC: 7.0 Clang: 4.0 MSVC: VS 2017 15.5

This feature resolves Core issue CWG 150.

From the paper:

This paper allows a template template-parameter to bind to a template argument whenever the template parameter is at least as specialized as the template argument. This implies that any template argument list that can legitimately be applied to the template template-parameter is also applicable to the argument template.


template <template <int> class> void FI();
template <template <auto> class> void FA();
template <auto> struct SA { /* ... */ };
template <int> struct SI { /* ... */ };
FI<SA>();  // OK; error before this paper
FA<SI>();  // error

template <template <typename> class> void FD();
template <typename, typename = int> struct SD { /* ... */ };
FD<SD>();  // OK; error before this paper (CWG 150)

(Adapted from the comment by IncongruentModulo1)

For a useful example, consider something like this:

template <template <typename> typename Container>
struct A
    Container<int>    m_ints;
    Container<double> m_doubles;

In C++14 and earlier, A<std::vector> wouldn’t be valid (ignoring the typename and not class before container) since std::vector is declared as:

template <typename T, typename Allocator = std::allocator<T>> class vector;

This change resolves that issue. Before, you would need to declare template <template <typename...> typename Container>, which is more permissive and moves the error to a less explicit line (namely the declaration of m_ints wherever the struct A is implemented /declared, instead of where the struct is instantiated with the wrong template type.



GCC: 6.0 Clang: 3.7 MSVC: 14.0

More background in the original paper: PDF: N4152 and GOTW issue 47: Uncaught Exceptions.

The function returns the number of uncaught exception objects in the current thread.

This might be useful when implementing proper Scope Guards that works also during stack unwinding.

A type that wants to know whether its destructor is being run to unwind this object can query uncaught_exceptions in its constructor and store the result, then query uncaught_exceptions again in its destructor; if the result is different, then this destructor is being invoked as part of stack unwinding due to a new exception that was thrown later than the object’s construction

The above quote comes from PDF: N4152.

constexpr if-statements  


GCC: 7.0 Clang: 3.9 MSVC: 15.3

The static-if for C++! This allows you to discard branches of an if statement at compile-time based on a constant expression condition.

if constexpr(cond)
     statement1; // Discarded if cond is false
     statement2; // Discarded if cond is true

This removes a lot of the necessity for tag dispatching and SFINAE:


template <typename T, std::enable_if_t<std::is_arithmetic<T>{}>* = nullptr>
auto get_value(T t) {/*...*/}

template <typename T, std::enable_if_t<!std::is_arithmetic<T>{}>* = nullptr>
auto get_value(T t) {/*...*/}

Tag dispatching

template <typename T>
auto get_value(T t, std::true_type) {/*...*/}

template <typename T>
auto get_value(T t, std::false_type) {/*...*/}

template <typename T>
auto get_value(T t) {
    return get_value(t, std::is_arithmetic<T>{});

if constexpr

template <typename T>
auto get_value(T t) {
     if constexpr (std::is_arithmetic_v<T>) {
     else {


Library Features  

To get more details about library implementation I suggest those links:

This section only mentions some of the most important parts of library changes, it would be too impractical to go into details of every little change.

Merged: The Library Fundamentals 1 TS (most parts)  


We get the following items:

The wording from those components comes from Library Fundamentals V2 to ensure the wording includes the latest corrections.


Removal of some deprecated types and functions, including std::auto_ptr, std::random_shuffle, and old function adaptors  


  • Function objects - unary_function/binary_function, ptr_fun(), and mem_fun()/mem_fun_ref()
  • Binders - bind1st()/bind2nd()
  • auto_ptr
  • Random shuffle - random_shuffle(first, last) and random_shuffle(first, last, rng)

Merged: The Parallelism TS, a.k.a. “Parallel STL.”,  


Parralel versions/overloads of most of std algorithms. Plus a few new algorithms, like reduce, transform_reduce, for_each.

std::vector<int> v = genLargeVector();

// standard sequential sort
std::sort(v.begin(), v.end());

// explicitly sequential sort
std::sort(std::seq, v.begin(), v.end());

// permitting parallel execution
std::sort(std::par, v.begin(), v.end());

// permitting vectorization as well
std::sort(std::par_unseq, v.begin(), v.end());


Merged: File System TS,  


namespace fs = std::filesystem;

fs::path pathToShow(/* ... */);
cout << "exists() = " << fs::exists(pathToShow) << "\n"
     << "root_name() = " << pathToShow.root_name() << "\n"
     << "root_path() = " << pathToShow.root_path() << "\n"
     << "relative_path() = " << pathToShow.relative_path() << "\n"
     << "parent_path() = " << pathToShow.parent_path() << "\n"
     << "filename() = " << pathToShow.filename() << "\n"
     << "stem() = " << pathToShow.stem() << "\n"
     << "extension() = " << pathToShow.extension() << "\n";


Merged: The Mathematical Special Functions IS,  

PDF - WG21 P0226R1

Improving std::pair and std::tuple  


std::shared_mutex (untimed)  




Variant is a typesafe union that will report errors when you want to access something that’s not currently inside the object.

std::variant<std::string, int> v { "Hello A Quite Long String" };
// v allocates some memory for the string
v = 10; // we call destructor for the string!
// no memory leak


  • Variant is not allowed to allocate additional (dynamic) memory.
  • A variant is not permitted to hold references, arrays, or the type void.
  • A variant is default initialized with the value of its first alternative.
  • If the first alternative type is not default constructible, then the variant must use std::monostate as the first alternative

Have a look at more example in a separate article:

Everything You Need to Know About std::variant from C++17 - C++ Stories

Splicing Maps and Sets  


From Herb Sutter, Oulu trip report:

You will now be able to directly move internal nodes from one node-based container directly into another container of the same type. Why is that important? Because it guarantees no memory allocation overhead, no copying of keys or values, and even no exceptions if the container’s comparison function doesn’t throw.

New functions:

  • std::map::extract
  • std::map::merge
  • std::set::extract
  • std::set::merge

Elementary string conversion  

from_chars and to_chars which are low-level, and offers the best possible performance

The new conversion routines are:

  • non-throwing
  • non-allocating
  • no locale support
  • memory safety
  • error reporting gives additional information about the conversion outcome
  • bound checked
  • explicit round-trip guarantees - you can use to_chars and from_charsto convert the number back and forth, and it will give you the exact binary representations. This is not guaranteed by other routines like printf/sscanf/itoa, etc.

Simple example:

std::string str { "xxxxxxxx" };
const int value = 1986;
std::to_chars(, + str.size(), value);

// str is "1986xxxx"

more in:


A handy helper for std::tuple. It takes a tuple and a callable object and then invokes this callable with parameters fetched from the tuple.

#include <iostream>
#include <tuple>
int sum(int a, int b, int c) { 
    return a + b + c; 

void print(std::string_view a, std::string_view b) {
    std::cout << "(" << a << ", " << b << ")\n";

int main() {
    std::tuple numbers {1, 2, 3};
    std::cout << std::apply(sum, numbers) << '\n';

    std::tuple strs {"Hello", "World"};
    std::apply(print, strs);

See more in C++ Templates: How to Iterate through std::tuple: std::apply and More - C++ Stories


With std::invoke you get access to INVOKE expression that was defined in the Standard since C++11 (or even in C++0x, TR1), but wasn’t exposed outside.

It can handle the following callables:

  • function objects: like func(arguments...)
  • pointers to member functions (obj.*funcPtr)(arguments...)
  • pointer to member data obj.*pdata

In C++20 it was improved and marked with constexpr.

See more in C++20 Ranges, Projections, std::invoke and if constexpr - C++ Stories


This is a place for you to be mentioned!



Thanks for all the support with the list!

There are still items that should be updated, but the list is mostly done.

If you want to read more about C++17, have a look at my book (-17% less with this coupon code, till 10th December 2022)

C++17 In Detail, -17% coupon code