c++ – What are the new features of C ++ 17?


Translation based on Yakk's question from the OS in English .

All the features of C ++ 17 have already been approved, so it is unlikely to see any major changes. Hundreds of proposals were made for C ++ 17.

Which of these features were added to C ++ in C ++ 17?


Language characteristics:

Templates and Generic Code

Deduction of template argument for template classes.

Technical document p0091r2 .

Suppose we have the following definitions:

std::vector<int> vi1 = { 0, 1, 1, 2, 3, 5, 8 };
std::vector<int> vi2;
std::mutex m;
unique_lock<std::mutex> ul(m, std::defer_lock);

template<class Func> class Foo() { 
    Foo(Func f) : func(f) {} 
    void operator()(int i) { 
        os << "Calling with " << i << std::endl;
    Func func; 
    std::mutex mtx;

Before the C ++ 17 standard, the following objects must be constructed as shown:

pair<int, double> p(2, 4.5);
auto t = make_tuple(4, 3, 2.5);
copy_n(vi1, 3, back_inserter(vi2));

// Virtualmente imposible pasar una lambda al constructor
// de una clase plantilla sin declarar la lambda
for_each(vi2.begin(), vi2.end(), Foo<???>([&](int i) { ...}));

lock_guard<std::mutex> lck(foo.mtx);
// Notacion del documento tecnico N4470
lock_guard<std::mutex, std::unique_lock<std::mutex>> lck2(foo.mtx, ul);

auto hasher = [](X const & x) -> size_t { /* ... */ };
unordered_map<X, int, decltype(hasher)> ximap(10, hasher);

If we allow the compiler to deduce the arguments for the template class constructors, we can replace the above code with:

pair p(2, 4.5);
tuple t(4, 3, 2.5);
copy_n(vi1, 3, back_insert_iterator(vi2));

// Ahora esto es facil, en lugar de virtualmente impossible
for_each(vi.begin(), vi.end(), Foo([&](int i) { ...}));

auto lck = lock_guard(foo.mtx);
lock_guard lck2(foo.mtx, ul);

// NOTA: la deducción de argumentos de plantilla deduce
// los argumentos no explícitos
unordered_map<X, int> ximap(10, [](X const & x) -> size_t { /* ... */ });

Declare non-type template arguments with auto .

Technical document p0127r1 .

The type of a non-type template parameter must be explicitly specified, this causes unnecessary verbosity and reduces the flexibility of those templates that are intended to receive constant arguments of any type:

template <typename T, T v> struct S { }; // Definicion
S<decltype(x), x> s;                     // Instanciacion

The example uses decltype to get the type of x (a compile-time constant) before passing both parameters to S Ideally, you should modify the declaration of S so that the type of x not required:

S<x> s; // Instanciacion deseada

This is achieved by allowing the use of auto in the list of template parameters:

template <auto v> struct S; // Deduce el tipo de v

Allow constant evaluation of all nontype template arguments.

Technical document n4198 .

Before C ++ 17, C ++ allowed the following nontype template parameters:

  • Any Integral or Enumerated Type that is a constant expression.
  • Pointers that point to objects in static memory, bounding functions, or constant expressions that evaluate to a null pointer.
  • References to objects in static memory or functions with binding.
  • Pointers to members &X::y or constant expressions that evaluate to a null-member-pointer.

As of C ++ 17 the nontyping template parameters can be:

  • Any Integral or Enumerated Type that is a constant expression.
  • Pointers that point to an entire object in static memory, a function, or a null pointer.
  • References to a glvalue referencing an entire object in static memory or a function.
  • Any constant pointer-to-member expression.
  • std::nullptr_t .

Allow typename in template-parameters (template template)

Technical document n4051

It is mandatory to use class for the type of a template-parameter:

template<template<typename> class X> struct S; // class es requerido

As of C ++ 17 you will also be able to:

template<template<typename> typename X> struct S; // Se puede usar class o typename

Folding expressions 1 .

Technical document n4295 .

A folding expression folds a template parameter package onto a folding binary operator.

The folding operators are:

+  -  *  /  %  ^  &  |  ~  =  <  >  <<  >>
+=  -=  *=  /=  %=  ^=  &=  |=  <<=  >>=
==  !=  <=  >=  &&  ||  ,  .*  ->*
  • An expression of the type (... operador expresión) where op is a folding operator is known as a left unary fold.
  • An expression of the type (expresión operador ...) where op is a folding operator is known as a right unary fold.

The left and right unary folds are known as unary folds. In a unary fold, the expression must contain an unexpanded parameter package.

  • An expression of the type (expresión1 operador1 ... operador2 expresión2) in which operador1 and operador2 are folding operators is known as a binary fold.

In a binary fold, operador1 and operador2 must be the same folding operator and expresión1 or expresión2 must contain a package of unexpanded parameters but not both. If expresión2 contains an unexpanded parameter package, the expression is known as a binary left fold. If expresión1 contains an unexpanded parameter package, the expression is known as a binary right fold.

Instantiating a collapsible expression produces:

  • ((Expr1 operador Expr2) operador ...) operador ExprN for left unary folds.
  • Expr1 operador (... operador (ExprN-1 operador ExprN)) for right unary folds.
  • (((Expr operador Expr1) operador Expr2) operador ...) operador ExprN for binary left folds.
  • Expr1 operador (... operador (ExprN-1 operador (ExprN operador Expr))) for binary right folds.

In each case:

  • operador is the folding operator
  • N is the number of elements in the expansion of the parameter package.
  • ExprX is generated by instantiating the pattern and replacing each parameter of the package with the X-th element.

For a binary folding expression, Expr is generated by instantiating the expression that does not contain an unexpanded parameter package:

template<typename... Args>
bool todos(Args... args) { return (args && ...); }

bool b = todos(true, true, true, false);

When we instantiate the template todos would todos obtain:

bool todos(bool e1, bool e2, bool e3, bool e4) { return ((e1 && e2) && e3) && e4; }

New deduction rules for auto with lists between keys.

Technical document n3922

In C ++ 17 the rules for deduction of types ( auto ) change for deductions involving lists of elements.

For list copy initialization:

  • auto type deduction deducts std::initializer_list if the types in the list are identical or else it will be a compilation error.

For list initialization:

  • In lists with only one element, auto will deduce the type of that element; prior to C ++ 17 you deduced a std::initializer_list from an element.
  • In lists with more than one element, auto will not deduce, it will be a compilation error.


auto a = { 1, 2 };   // deduce std::initializer_list<int> de dos elementos
auto b = { 3 };      // deduce std::initializer_list<int> de un elemento
auto c{ 3 };         // deduce int, antes deducia std::initializer_list<int> de un elemento

if constant.

Technical document p0128r1 .

An if whose condition is evaluated at compile time and the untaken branch is discarded from compilation, is useful for simplifying certain codes:

void funcion()
    // Se requiere la funcion vacia para eliminar la recursion

template <class T> 
void funcion(T&& elemento)
    // Hacer cosas con el elemento

template <class T, class... Parametros>
void funcion(T&& cabeza, Parametros&&... cola)
    funcion(cola...); // Finaliza la recursion cuando cola... es una lista vacia

With if constant, the empty function is not necessary:

template <class T> 
void funcion(T&& elemento)
    // Hacer cosas con el elemento

template <class T, class... Parametros>
void funcion(T&& cabeza, Parametros&&... cola)
    constexpr if (sizeof...(cola))
        // No se genera esta parte del codigo si cola... es una lista vacia


Lambdas constant expression

Technical document n4487 .

Before C ++ 17 it was forbidden for a lambda or its instance (closure) to be part of a constant expression; This restriction was inconsistent (since traditional functors do not have it) and unnecessary:

// Este codigo es valido en C++17 pero incorrecto en estandares previos
constexpr auto L = [](int i) { return i; }; // Correcto en C++17

auto L2 = [] { return 0; };
constexpr int I = L2(); // Correcto en C++17

Capture *this in lambdas

Technical document p0018r3 .

Lambdas declared in a non-static member function capture this implicitly or explicitly to access the object's variables through the this pointer; for this reason the capture of the object context by copy or by reference are the same:

struct S {
    int x ;

    void f() {
        // Ambas lambdas son identicas pese a que su captura sea diferente:
        auto a = [&]() { x = 42 ; } // El acceso a x se transforma en (*this).x = 42
        auto b = [=]() { x = 43 ; } // El acceso a x se transforma en (*this).x = 43

This causes problems in asynchronous programming as it can cause the use of memory pointers that is no longer valid:

class Tarea {
    int valor ;
    Tarea() : valor(42) {}
    std::future generar()
    { return std::async( [=]()->int{ return valor ; }); }

std::future foo()
    Tarea tmp ;
    // La closure asociada con el std::future devuelto
    // tiene un puntero this implicito.
    return tmp.generar();
    // El puntero this de la closure deja de ser valido
    // al finalizar la funcion, despues del return

int main()
    std::future f = foo();
    // El valor del futuro no es valido porque
    // el objeto que lo genero ya ha sido destruido
    assert( 42 == f.get() );
    return 0 ;

Being able to capture *this solves the problem.


Attributes were added in C ++ 11 and C ++ 17 adds new attributes.

Attributes [[fallthrough]] , [[nodiscard]] and [[unused]]

Technical document p0068r0 .

The [[fallthrough]] attribute is used as an instruction, the [[fallthrough]] instruction is put just before a case tag on a switch and its goal is to mark that execution will fall to the next case tag intentionally:

switch (c) {
    case 'a':
        f(); // WARNING: falta break
    case 'b':
        [[fallthrough]]; // Correcto
    case 'c':

The [[nodiscard]] attribute can be used on types or on functions; If it has been marked like this and the type or function return is not used in the code, a compilation alarm will be generated:

[[nodiscard]] int funcion();
void metodo() {
    funcion(); // WARNING: valor de retorno de una funcion nodiscard es descartado.

[[nodiscard]] struct S { ... };
S crear_S();

void funcion() {
    crear_S(); // WARNING: valor de retorno de un type nodiscard es descartado.

The [[unused]] attribute marks an entity that for some reason is not used. If the compiler were to mark that entity with an alarm, it will not do so if the attribute is used:

// Una de las dos funciones no se usa dependiendo de UNA_CONDICION
static void funcion1() { ... }
static void funcion2() { ... } // warning: funcion2 no se usa

void iface() {

This attribute does not prevent the compiler from compiling the marked entity, so both functions will be compiled but neither will be marked with an alarm.

[[unused]] static void funcion1() { ... }
[[unused]] static void funcion2() { ... }

Attributes in Enumerated and Namespaces

Technical document n4266 .

Starting with C ++ 17, it is allowed to mark namespaces and enumerations with attributes.


inline variables.

Technical document n4424 .

C ++ requires that extern functions and variables have exactly one definition in the program, this requirement was relaxed for functions by the inline qualifier. Inline functions can be defined at various points, but all their definitions must be the same; the same is true for functions and variables instantiated from templates. Before C ++ 17 there was nothing similar for non-template variables although it is not strange to need a global and unique object without it having to be defined in a single translation unit.

Defining nested namespaces

Technical document n4230 .

As of C ++ 17 this code:

namespace A::B::C // Error en estandares previos a C++17
// codigo

It will be equivalent to:

namespace A
namespace B
namespace C
// codigo

Extend static_assert .

Technical document n3928 .

As of C ++ 17 the second parameter of static_assert is optional:

static_assert ( true  != true ); // Correcto en C++17, incorrecto en estandares anteriores
static_assert ( false != false , "Falso debe ser falso" );

Clearer multiple return and flow control.

Structured Links 1

Technical document p0217r2 .

C ++ 17 offers the ability to declare multiple initialized variables from a tuple or structure:

std::tuple<T1,T2,T3> crear_tupla(/*...*/) { /*...*/ return { a, b, c }; }
auto [ x, y, z ] = crear_tupla(); // x deduce tipo T1, y deduce tipo T2, z deduce tipo T3

// iterador deduce tipo typeof(mapa)::iterator, insertado deduce bool
const auto [iterador, insertado] = mapa.insert({"clave", valor});

struct S { int x; volatile double y; };
S f();
// x deduce const int, y deduce const volatile double
const auto [ x, y ] = f();

Initializer in if .

Technical document p0305r0 .

C ++ allows you to declare and initialize a variable in the if , if this variable is evaluable as a Boolean condition, it will enter the true branch of the statement; but this is not possible when the variable is not evaluable as boolean:

if (int no_multiplo_de_3 = x % 3)
std::cout << no_multiplo_de_3 << " no es multiplo de 3\n";

// Error! decltype(mapa)::iterator no es evaluable como condicion
if (auto iterador = mapa.find("clave"))
{ ... }

C ++ 17 allows you to separate the declaration of the variable and its evaluation in the if :

if (auto iterador = mapa.find("clave"); iterador != mapa.end())
    std::cout << "clave existe en el mapa\n";

// Combinado con enlazados estructurados
if (const auto [iterador, insertado] = mapa.insert({clave, valor}); insertado)
    std::cout << clave << " ha sido insertada en el mapa\n";

Range for loop generalization.

Technical document p0184r0 .

Allows the type of the begin iterator to be different from the type of the end iterator.


Hexadecimal literals of floating-point numbers

Technical document p0245r1 .

There is a base-2 scientific notation for hexadecimal base floating-point numeric values: 0x3.ABCp-10. The mantissa is expressed in hexadecimal and the exponent in interpreted decimal with respect to base 2. The smallest IEEE-754 standard expressible simple precision value would be 0x1.0p-126 .

Dynamic memory allocation for aligned data.

Technical document p0035r1 .

C ++ 11 added the ability to specify alignment for objects, but did not specify any mechanism to properly allocate dynamic memory to these aligned objects, in C ++ 17 overloads are added to the new and delete operators to specify alignment:

void* operator new(std::size_t size, std::align_val_t alignment);
void operator delete[](void* ptr, std::align_val_t alignment) noexcept;
void operator delete[](void* ptr, std::align_val_t alignment, std::size_t size) noexcept;

Elision of guaranteed copy.

Technical document p0135r0 .

In C ++ 17 it is guaranteed that certain copies will be elided, in particular: when the object to be elided is a temporary value.

Refinement of the evaluation order of expressions for idiomatic C ++.

Technical document p0145r2 .

Progress guarantees for the Parallelism V2 technical specification.

Technical document p0299r0 .

UTF8 character literals.

Technical document n4267 .

C ++ 11 added the UTF text string literals:

auto utf8     = u8"UTF8"; // Literal de cadena de texto codificado en UTF8
auto utf16    = u"UTF16"; // Literal de cadena de texto codificado en UTF16
auto utf32    = U"UTF32"; // Literal de cadena de texto codificado en UTF32

C ++ 17 adds the UTF text character literals:

auto utf8     = u8'8'; // Literal de caracter codificado en UTF8
auto utf16    = u'F';  // Literal de caracter codificado en UTF16
auto utf32    = U'X';  // Literal de caracter codificado en UTF32

Specifying exceptions as part of the type of the function.

Technical document p0012r1 .

Specifying exceptions is not part of the function's type in pre-C ++ 17 standards:

void f_throw() throw(int) {}
void f_no_throw() {}

using void_throw = void() throw(int);
// Presuntamente un puntero a funcion void que puede lanzar excepciones
void_throw *p;

p = f_throw;    // Correcto
p = f_no_throw; // Correcto tambien!

As of C ++ 17 an error would be generated:

p = f_throw;    // Correcto
p = f_no_throw; // ERROR: No se puede convertir void() a void() throw(int)

Static verification of headers.

Technical document p0061r1 .

The __has_include instruction __has_include allow you to check if a header is available

#if __has_include(<optional>)
#  include <optional>
#elif __has_include(<experimental/optional>)
#  include <experimental/optional>

Fixes on conversions from arrays to pointers.

Technical document n4261 .

Fixes on legacy constructors.

Technical document p0136r1 .

What's new in STL

Type of data

std::variant Secure-typed union.

Technical document p0088r2 .

std::optional Object that may or may not contain an arbitrary value.

Technical document n4480 .

std::any Object that can contain different types.

Technical document n4480 .

std::string_view Reference to text strings.

Technical document n4480 .

std::shared_mutex shared mutex.

Technical document n4480 .


std::invoke Generic call object.

Technical document n4169 .

You can call any callable object: function pointer, function, member function … with a unified syntax.

std::apply Calls a function with a parameterized tuple.

Technical document n4480 .

File System Technical Specification v1

Technical document p0218r0 .

Parallelism Technical Specification v1

Technical document n4071 .

Library Fundamentals Technical Specification v1

Technical document p0220r1 .

Scroll to Top