Leyendo sobre el lenguaje C++ he visto que algunos desarrolladores hablan de C++17 y no me queda claro si es lo mismo que C++ o es otro lenguaje.
¿Qué es C++17?
Leyendo sobre el lenguaje C++ he visto que algunos desarrolladores hablan de C++17 y no me queda claro si es lo mismo que C++ o es otro lenguaje.
¿Qué es C++17?
Es la sexta revisión del Lenguaje de Programación C++, no se llama C++6 porque las revisiones no se numeran por el orden de revisión si no por el año en que la Organización Internacional para la Estandarización (ISO) dan el visto bueno a la revisión del lenguaje (también conocida como Dialecto del Lenguaje de Programación).
Este es un breve resumen de la historia del lenguaje C++:
Así pues, C++17 no es más que una revisión del lenguaje C++ ¿Qué añade esta revisión respecto a las versiones anteriores?
Deducción de parámetros en objetos plantilla.Los objetos plantilla pueden deducir los parámetros de plantilla de forma parecida a cómo se deducen en las funciones plantilla:
template <typename T>
void funcion(T t) { /* ... */ }
template <typename T>
struct objeto
{
objeto(T t) : valor{t} {}
T valor;
};
int main()
{
funcion(0); // Correcto, deduce int t
funcion('Z'); // Correcto, deduce char t
objeto o1(0); // Correcto, deduce int valor. Antes de C++17 es error.
objeto o2('Z'); // Correcto, deduce char valor. Antes de C++17 es error.
return 0;
}
Sin embargo, esta deducción no funciona exactamente igual que la deducción de plantillas función, pues no da soporte a deducción parcial.
Deducción de tipos en parámetros plantilla no-tipo.Se puede usar auto
como parámetro plantilla para que la plantilla deduzca el tipo subyacente del parámetro no-tipo facilitado:
// vvvv <-- Antes de C++17 no se permite usar auto aqui.
template <auto T>
void funcion() { /* ... */ }
static int i = 0;
int main()
{
funcion<0>(); // Correcto, T sera de tipo int con valor 0
funcion<'Z'>(); // Correcto, T sera de tipo char con valor 'Z'
funcion<&i>(); // Correcto, T sera de tipo int * con valor 0x????????
funcion<3.14>(); /* Error, T deberia ser de tipo double,
pero double y float no se permiten como parametro no-tipo */
funcion<int>(); // Error, se esperaba un valor no un tipo.
return 0;
}
Permitir typename
en plantillas-plantilla.
Al declarar una plantilla-plantilla ahora se puede usar indistintamente typename
o class
(antes no se podía):
// Correcto en C++17 y anterior --> vvvvv
template <typename T, template<typename> class P>
void funcion1(P<T>) { /* ... */ }
// Correcto solo a partir de C++17 --> vvvvvvvv
template <typename T, template<typename> typename P>
void funcion2(P<T>) { /* ... */ }
Expresiones plegables.
Es posible aplicar ciertos operadores sobre paquetes de parámetros en plantillas variadicas:
template <typename ... args>
auto funcion(args ... a) { return (a + ...); }
// Expresion plegada --> ^^^^^^^^^
int main()
{
// Devuelve 21
std::cout << funcion(1, 2, 3, 4, 5, 6); // Se despliega en return 1 + 2 + 4 + 4 + 5 + 6;
// Devuelve hola mundo
std::cout << funcion("hola"s, ' ', "mundo"s); // Se despliega en return "hola"s + ' ' + "mundo"s;
return 0;
}
Los operadores plegables son: +
, -
, *
, /
, %
, ^
, &
, |
, ~
, =
, <
, >
, <<
, >>
, +=
, -=
, *=
, /=
, %=
, ^=
, &=
, |=
, <<=
, >>=
, ==
, !=
, <=
, >=
, &&
, ||
, ,
, .*
y ->*
y pueden desplegarse así:
( expresion operador ... )
( ... operador expresion )
( expresion operador ... operador expresion )
Nuevas normas de deducción para valores entre llaves ({}
).
Cuando entre llaves sólo haya un valor se deducirá como valor, no como lista:
auto a{'a'}; // a es de tipo char, antes deducia std::initializer_list<char> de un elemento.
auto b{0xb}; // b es de tipo int, antes deducia std::initializer_list<int> de un elemento.
auto c{1.f}; // c es de tipo float, antes deducia std::initializer_list<float> de un elemento.
Expandir paquetes de parámetros en cláusulas using
.
Teniendo un objeto base como el siguiente:
template <typename T>
struct base
{
void f(T) { std::cout << __PRETTY_FUNCTION__ << '\n'; }
};
Y un objeto que deriva de varios base
:
template <typename ... T>
struct derivado : base<T> ...
{
};
Podemos traer al ámbito de derivado
todas las versiones de base<T>::f(T)
con una cláusula using:
template <typename ... T>
struct derivado : base<T> ...
{
using base<T>::f ...;
};
derivado<int, double, char> d;
/* Equivale a:
using base<int>::f;
using base<double>::f;
using base<char>::f; */
Lambdas expresión constante.
Si la lambda cumple los requisitos para ser una expresión constante, podrá usarse como tal:
// vvvvvvvvv <-- No se puede usar constexpr aqui antes de C++17
constexpr auto cinco = [](){ return 5; };
int main(int argc, char **)
{
switch (argc)
{
// vvvvvvv <-- Valido de C++17 en adelante
case cinco(): /* ... */ break;
default: std::cout << /* ... */ break;
}
return 0;
}
Podría omitirse constexpr
en la declaración de la lambda pues serán expresiones constantes por defecto si cumplen con los requisitos para serlo.
*this
en lambdas.
En una lambda declarada dentro de un método de un objeto, las capturas genéricas capturan this
como puntero:
struct objeto
{
void f()
{
// v <-- Captura this por referencia
auto a = [&]() { valor = 100 ; }; // Equivale a this->valor = 100;
// v <-- Captura this por copia
auto b = [=]() { valor = 200 ; }; // Equivale a this->valor = 200;
// vvvvv <-- Copia el objeto
auto c = [*this]() mutable { valor = 300 ; } // Equivale a this->valor = 300;
a(); // this->valor contiene 100
b(); // this->valor contiene 200
c(); /* this->valor no se modifica, contiene 200.
Se modifica el this->valor de una copia */
}
int valor;
};
Atributo [[fallthrough]]
.
Indica al compilador que la ausencia de una instrucción break
al final de un case
en un switch
es intencionada:
switch (variable)
{
case 0:
// Hacer cosas...
case 1: // <-- ALarma! falta break!
// Hacer cosas...
[[fallthrough]];
case 2: // <-- Ok, la falta de break es intencionada.
// Hacer cosas...
break;
}
Atributo [[nodiscard]]
.
Indica al compilador que un tipo de dato o el retorno de una función no debe ser descartado.
// vvvvvvvvvvvvv <-- Se aconseja no descartar este dato
struct [[nodiscard]] informacion { /* ... */ };
struct datos { /* ... */ };
informacion obtener_informacion() { /* ... */ }
[[nodiscard]] datos obtener_datos() { /* ... */ }
// ^^^^^^^^^^^^^ <-- Se aconseja no descartar este retorno
int main()
{
obtener_informacion(); // Alarma! Se esta descartando un objeto de tipo informacion!
obtener_datos(); // Alarma! Se esta descartando el retorno de obtener_datos!
return 0;
}
Atributo [[maybe_unused]]
.
Indica al compilador que una entidad podría no estar siendo usada intencionadamente.
[[maybe_unused]] void debug() { std::cout << "Debug\n"; }
void f([[maybe_unused]] int valor)
{
std::cout << "F!\n";
}
El código anterior no mostrará alarmas por no usar la función debug
ni por no usar el valor valor
.
Es posible aplicar atributos sobre espacios de nombres y valores enumerados:
namespace [[deprecated]] utiles
{
void f() { /* ... */ }
}
enum puerto
{
defecto = 8080,
entrada = 8016,
[[deprecated]] salida = 8015
};
void datos(puerto p) { /* ... */ }
int main()
{
utiles::f(); // Alarma! el espacio de nombres f esta deprecado
datos(defecto);
datos(salida); // Alarma! puerto::salida esta deprecado
return 0;
}
Variables inline
.
Es posible hacer que las variables se comporten como funciones inline
, esto es: considerar múltiples definiciones idénticas de la variable como la misma definición.
El espacio de nombres:
namespace Primero
{
namespace Segundo
{
namespace Tercero
{
// Cosas...
}
}
}
Puede ser declarado como
namespace Primero::Segundo::Tercero
{
// Cosas...
}
Simplificación de static_assert
.
Ya no es obligatorio añadir un texto a static_assert
.
A partir de C++17 si una función no dispone de cláusula throw
en la firma se considera que no lanza excepciones.
Para funciones con múltiples valores de retorno (Que devuelvan std::pair
, std::tuple
, std::array
y agregados) es posible obtener cada valor de retorno por separado:
std::map<int, int> mii;
// i y b declarados por separado:
std::map<int, int>::iterator i;
bool b;
// +---- Asigna el retorno->first a i y el retorno->second a b
// | Valido en C++17 y anterior.
// vvvvvvvvv
std::tie(i, b) = mii.insert({1, 2});
/* iterador e insertado declarados en la misma línea, su tipo se deduce
de retorno->first y retorno->second respectivamente. Asigna el
retorno->first iterador y el retorno->second a insertado.
valido solo a partir de C++17 */
auto [iterador, insertado] = mii.insert({3, 4});
// ^^^^^^^^^^^^^^^^^^^^^ <--- Emparejamiento estructurado
struct abc { int a, b, c; };
abc f1() { return {2, 3, 5}; }
std::tuple<std::string, float, abc, int> f2() { return {"Hola mundo", 3.14f, {1, 2, 3}, 4}; }
std::array<int, 5> f3() { return {0, 1, 2, 3, 4}; }
// Asigna retorno.a a x, retorno.b a y, retorno.c a z
auto [x, y, z] = f1();
// ^^^^^^^^^ <--- Emparejamiento estructurado
// Asigna 1r elemento de la tupla a str, segundo a pi, tercero a xyz y cuarto a v
auto [str, pi, xyz, v] = f2();
// ^^^^^^^^^^^^^^^^^ <--- Emparejamiento estructurado
// Asigna retorno[0] a p0, retorno[1] a p1...
auto [p0, p1, p2, p3, p4] = f3();
// ^^^^^^^^^^^^^^^^^^^^ <--- Emparejamiento estructurado
Inicializador en estructuras de control.
Es posible declarar e inicializar variables en if
y switch
:
if (auto [iterador, insertado] = mii.insert({3, 4}); !insertado)
std::cout << "Elemento " << iterador->first << " repetido!\n";
else
std::cout << "Elemento " << iterador->first << "anyadido con valor " << iterador->second << '\n';
switch (int i = f1() + f2(); i)
{
/* ... */
}
Bucle for
de rango generalizado.
Ahora se permite que begin
y end
en el for de rango sean de diferentes tipos.
if
constante.
Se puede añadir constexpr
a la cláusula if
. Si la condición de la condición constante no se cumple el código dentro de dicha condición no se compilará.
#define DEBUG false
if constexpr (DEBUG)
{
// Codigo que debe ser compilado solo en modo DEBUG
}
WIP....
Añadiendo más cosas.