C++17 permite escribir código simple, más claro y más expresivo. Algunas de las características introducidas en C++17 son:
- Espacios de nombres anidados
- Declaración de variables en if y switch
- si declaración constexpr
- Encuadernaciones estructuradas
- Expresiones de pliegue
- Inicialización de lista directa de enumeraciones
Espacios de nombres anidados
Los espacios de nombres son una herramienta muy conveniente para organizar y estructurar el código base, juntando componentes como clases y funciones que lógicamente pertenecen al mismo grupo.
Consideremos un código base hipotético de un motor de videojuegos. Aquí, definió un espacio de nombres para todo el motor del juego, por lo que todas las clases y funciones implementadas en este motor del juego se declararán bajo este espacio de nombres común. Para hacer definiciones más claras, puede definir otro espacio de nombres bajo el espacio de nombres global, digamos Graphics , que es un subespacio de nombres , ahora coloque todas las clases que realizan operaciones gráficas bajo ese espacio de nombres y así sucesivamente.
- Antes de C++17:
a continuación se muestra la sintaxis utilizada para el espacio de nombres anidado:
C++
// Below is the syntax for using // the nested namespace namespace Game { namespace Graphics { namespace Physics { class 2D { .......... }; } } }
- Cuando C++17:
Antes de C++ 17, debe usar esta sintaxis detallada para declarar clases en espacios de nombres anidados, pero C++ 17 ha introducido una nueva característica que hace posible abrir espacios de nombres anidados sin esta sintaxis agitada que requiere la palabra clave repetida del espacio de nombres y realizar un seguimiento de llaves de apertura y cierre. En C++17 hay una sintaxis simple y concisa que usa dos puntos dobles para introducir espacios de nombres anidados . La sintaxis es la siguiente:
C++
// Below is the syntax to use the // nested namespace in one line namespace Game::Graphics::Physics { class 2D { .......... }; }
Esto hace que el código sea menos propenso a errores ya que no es necesario prestar atención a varios niveles de llaves.
Declaración de variables en if y switch
Antes de C++17:
Supongamos un vector de strings y desea reemplazar una string «abc» con «$$$» si está presente en el vector. Para hacerlo, puede invocar la función estándar find() para buscar un elemento en un vector como se muestra a continuación:
C++
// Below is the approach for replace // any string with another string // in vector of string vector<string> str // Find and replace abc with $$$ const auto it = find(begin(str), end(str), "abc"); if (it != end(str)) { *it = "$$$"; }
Explicación:
- El algoritmo de búsqueda devolverá un iterador que apunta a la string coincidente.
- Ahora, si nuevamente queremos reemplazar otra string con alguna otra string en el mismo vector, entonces para esto, siga el mismo enfoque que se muestra arriba, y como repetirá el mismo código solo tendrá que cambiar el nombre del iterador a algo más.
- Como declarar más de dos iteradores con el mismo nombre en el mismo ámbito dará un error de compilación . La opción de cambio de nombre funcionará, pero si hay varias strings que deben reemplazarse, este enfoque no es eficiente.
When C++17:
para tratar estos casos, C++17 ofrece una mejor opción al declarar variables dentro de las declaraciones if, como se muestra a continuación:
C++
// Below is the syntax for replacing a // string in vector of string in C++17 if (const auto it = find(begin(str), end(str), "abc"); it != end(str)) { *it = "$$$"; }
Ahora el alcance del iterador «it» está dentro de la declaración if en sí, y el mismo nombre del iterador también se puede usar para reemplazar otras strings.
Lo mismo se puede hacer usando una declaración de cambio para usar la sintaxis que se proporciona a continuación:
switch (initial-statement; variable) { .... // Cases }
A continuación se muestra el programa que reemplaza algunas strings definidas en el vector dado que se ejecuta solo en C++17:
C++
// C++ 17 code to demonstrate if constexpr #include <algorithm> #include <iostream> #include <string> #include <vector> using namespace std; // Helper function to print content // of string vector void print(const string& str, const vector<string>& vec) { cout << str; for (const auto& i : vec) { cout << i << " "; } cout << endl; } // Driver Code int main() { // Declare vector of string vector<string> vec{ "abc", "xyz", "def", "ghi" }; // Invoke print helper function print("Initial vector: ", vec); // abc -> $$$, and the scope of "it" // Function invoked for passing // iterators from begin to end if (const auto it = find(begin(vec), end(vec), "abc"); // Check if the iterator reaches // to the end or not it != end(vec)) { // Replace the string if an // iterator doesn't reach end *it = "$$$"; } // def -> ### // Replace another string using // the same iterator name if (const auto it = find(begin(vec), end(vec), "def"); it != end(vec)) { *it = "###"; } print("Final vector: ", vec); return 0; }
Producción:
Si declaración constexpr
Esta característica de C++ 17 es muy útil cuando escribe código de plantilla. La condición normal de la sentencia if se ejecuta en tiempo de ejecución, por lo que C++17 introdujo esta nueva sentencia if constexpr . La principal diferencia es que si constexpr se evalúa en tiempo de compilación . Básicamente, la función constexpr se evalúa en tiempo de compilación. Entonces, ¿por qué es esto importante? Su principal importancia va con el código de la plantilla.
Antes de C++17:
Suponga que, para comparar una variable entera con un valor, luego declare e inicialice ese entero en tiempo de compilación solo antes de usar esa variable como se muestra a continuación:
C++
// Below is the syntax for using // If-else statement int x = 5; // Condition if (x == 5) { // Do something } else { // Do something else }
Cuando C++17:
Supongamos que tiene alguna plantilla que opera en algún tipo T genérico .
C++
// Below is the generic code for // using If else statement template <typename T> // Function template for illustrating // if else statement auto func(T const &value) { if constexpr(T is integer) { // Do something } else { // Something else } }
Entonces, un aspecto de constexpr es que ahora el compilador sabe si T es un número entero o no, y el compilador considera solo la subdeclaración que satisface la condición, por lo que solo se compila ese bloque de código y el compilador de C++ ignora las otras subdeclaraciones.
A continuación se muestra el programa para el mismo:
C++
// C++ 17 code to demonstrate error // generated using if statement #include <iostream> #include <string> #include <type_traits> using namespace std; // Template Class template <typename T> auto length(T const& value) { // Check the condition with if // statement whether T is an // integer or not if (is_integral<T>::value) { return value; } else { return value.length(); } } // Driver Code int main() { int n{ 10 }; string s{ "abc" }; cout << "n = " << n << " and length = " << length(n) << endl; cout << "s = " << s << " and length = " << length(s) << endl; }
Producción:
error: request for member 'length' in 'value', which is of non-class type 'const int'
Explicación:
- En el código anterior, si el programa está compilado, dará un error de compilación porque el número entero no tiene una función llamada longitud(), y como hemos usado solo si la instrucción se compilará todo el código y dará un error.
- Para evitar este tipo de error, es decir, considere solo el código que es importante. C++17 es para el rescate.
- Entonces, al reemplazar if con if constexpr , si T es un número entero, entonces solo se compilará la condición bajo if constexpr (ya que satisface la condición de que T sea un número entero) y no la otra parte que contiene la función length() ( que produjo un error).
- El bloque else se considerará solo cuando T no sea un número entero, por ejemplo, strings, ya que tiene la función length(), no producirá un error e imprimirá la longitud de la string.
C++
// C++ 17 code to demonstrate if constexpr #include <iostream> #include <string> #include <type_traits> using namespace std; // Template Class template <typename T> auto length(T const& value) { // Check the condition with if // statement whether T is an // integer or not if constexpr(is_integral<T>::value) { return value; } else { return value.length(); } } // Driver Code int main() { int n{ 10 }; string s{ "abc" }; cout << "n = " << n << " and length = " << length(n) << endl; cout << "s = " << s << " and length = " << length(s) << endl; }
Producción:
Enlaces de estructura
Básicamente, le permite declarar múltiples variables que se inicializan con valores de pares, tuplas genéricas o estructuras personalizadas y estas declaraciones de variables múltiples ocurren en declaraciones individuales.
Antes de C++17:
Antes de C++17, std::tie se usaba para declarar varias variables que se inicializaban con valores de estructuras personalizadas.
C++
// Using tuple int a, b, c; std::tie(a, b, c) = std::make_tuple(1, 2, 3);
Cuando C++17:
Suponga que tiene un diccionario que tiene nombres como claves y su idioma favorito como valores y esto se implementa usando un mapa de contenedor estándar y desea insertar una nueva entrada usando el método de inserción. Este método de inserción devuelve un std::pair que contiene dos piezas de información, el primer elemento del par es un iterador y el segundo elemento es un valor booleano.
C++
// Below is the code to use // structure binding in C++17 map<string, string> fav_lang{ { "John", "Java" }, { "Alex", "C++" }, { "Peter", "Python" } }; auto result = fav_lang.insert({ "Henry", "Golang" });
Hay dos casos a considerar aquí:
- Si nuevo no está presente en el diccionario o ya está presente. Si la nueva asociación (par clave-valor) no está presente en el diccionario, se inserta. Entonces, en este caso, el par devuelto contiene un iterador que apunta al nuevo elemento y el valor booleano se convierte en True.
- Si la nueva clave ya está presente, el iterador apunta a la clave existente y el valor booleano se vuelve falso.
.primero .segundo
- Uso de enlaces de estructura C++ 17 para declarar e inicializar dos variables con nombres más significativos que primero y segundo.
- Usar los nombres de posición y éxito es mucho más claro que usar primero y segundo.
- El significado de posición y éxito es muy sencillo, es decir, la posición indica dónde está el iterador y el éxito indica si el elemento está insertado o no.
C++
// C++ 17 program to demonstrate // Structure Bindings #include <iostream> #include <map> #include <string> using namespace std; // Driver Code int main() { // Key-value pair declared inside // Map data structure map<string, string> fav_lang{ { "John", "Java" }, { "Alex", "C++" }, { "Peter", "Python" } }; // Structure binding concept used // position and success are used // in place of first and second auto[process, success] = fav_lang.insert({ "Henry", "Golang" }); // Check boolean value of success if (success) { cout << "Insertion done!!" << endl; } // Iterate over map for (const auto & [ name, lang ] : fav_lang) { cout << name << ":" << lang << endl; } return 0; }
Producción:
Expresiones plegables
C++ 11 dio la opción de plantillas variadas para trabajar con un número variable de argumentos de entrada. Las expresiones de pliegue son una nueva forma de desempaquetar parámetros variádicos con operadores. La sintaxis es la siguiente:
(paquete op…)
(… op paquete)
(paquete op… op init)
(init op… op paquete)
donde paquete representa un paquete de parámetros sin expandir, op representa un operador e init representa un valor.
- (paquete op…): Este es un pliegue derecho que se expande como paquete1 op (… op (paqueteN-1 op paqueteN)).
- (… op pack): Este es un pliegue izquierdo que se expande como ((pack1 op pack2) op …) op packN.
- (pack op… op init): Este es un doblez binario a la derecha que se expande como pack1 op (… op (packN-1 op (packN op init))).
- (init op … op pack): Este es un doblez binario a la izquierda que se expande como (((init op pack1) op pack2) op …) op packN.
Antes de C++17:
Para hacer una función que toma un número variable de argumentos y devuelve la suma de los argumentos.
C++
// Below is the function that implements // folding expressions using variable // number of arguments int sum(int num, ...) { va_list valist; int s = 0, i; va_start(valist, num); for (i = 0; i < num; i++) s += va_arg(valist, int); va_end(valist); return s; }
Cuando C++17:
para implementar una función recursiva como suma, etc. a través de plantillas variadas , esto se vuelve eficiente con C++17, que es mejor que las implementaciones de C++11. A continuación se muestra la clase de plantilla de la misma:
C++
// Template for performing the // recursion using variadic template auto C11_sum() { return 0; } // Template Class template<typename T1, typename... T> auto C11_sum(T1 s, T... ts) { return s + C11_sum(ts...); }
A continuación se muestra el programa para ilustrar lo mismo:
C++
// C++ program to illustrate the // folding expression in C++17 #include <iostream> #include <string> using namespace std; // Template Class template<typename... Args> auto sum(Args... args) { return (args + ... + 0); } template <typename... Args> auto sum2(Args... args) { return (args + ...); } // Driver Code int main() { // Function Calls cout << sum(11, 22, 33, 44, 55) << "\n"; cout << sum2(11, 22, 33, 44, 55) << "\n"; return 0; }
Producción:
Inicialización de lista directa de enumeraciones
En C++ 17 initialize se permite la inicialización de enumeraciones usando llaves. A continuación se muestra la sintaxis para el mismo:
byte de enumeración: carácter sin firmar {};
byte b {0}; // Aceptar
byte c {-1}; // ERROR
byte d = byte{1}; // OK
byte e = byte{256}; // ERROR
Algunas de las características de la biblioteca de C++17:
- std::byte{b}: es un tipo único que aplica el concepto de byte como se especifica en la definición del lenguaje C++. Un byte es una colección de bits y en este caso solo se pueden usar operadores bit a bit. A continuación se muestra el programa para ilustrar lo mismo:
C++
// Program to illustrate std::byte // in the C++ 17 #include <cstddef> #include <iostream> using namespace std; // Function to print byte a void Print(const byte& a) { cout << to_integer<int>(a) << endl; } // Driver Code int main() { byte b{ 5 }; // Print byte Print(b); // A 2-bit left shift b <<= 2; // Print byte Print(b); // Initialize two new bytes using // binary literals byte b1{ 0b1100 }; byte b2{ 0b1010 }; Print(b1); Print(b2); // Bit-wise OR and AND operations byte byteOr = b1 | b2; byte byteAnd = b1 & b2; // Print byte Print(byteOr); Print(byteAnd); return 0; }
Producción:
- std::filesystem(): proporciona una forma estándar de manipular directorios y archivos. En el siguiente ejemplo, un archivo se copió en una ruta temporal si hay espacio disponible. A continuación se muestra la plantilla para el mismo:
C++
// For manipulating the file // directories const auto FilePath {"FileToCopy"}; // If any filepath exists if(filesystem::exists(FilePath)) { const auto FileSize { filesystem::file_size(FilePath) }; filesystem::path tmpPath {"/tmp"}; // If filepath is available or not if(filesystem::space(tmpPath) .available > FileSize) { // Create Directory filesystem::create_directory( tmpPath.append("example")); // Copy File to file path filesystem::copy_file(FilePath, tmpPath.append("newFile")); } }
- std::apply(): sus parámetros son un objeto invocable que se va a invocar y una tupla cuyos elementos se deben usar como argumentos. A continuación se muestra la plantilla para el mismo:
C++
// Function that adds two numbers auto add = [](int a, int b) { return a + b; }; apply(add, std::make_tuple(11, 22));
- std::any(): La clase any describe un contenedor con seguridad de tipos para valores únicos de cualquier tipo. Las funciones de conversión any que no son miembros proporcionan acceso con seguridad de tipos al objeto contenido.