Saltar a contenido

Última actualización:2026-10-12

Diecisiete características avanzadas de C++17

Repaso completo a 17 características avanzadas de C++17

C++17 es hoy una de las versiones más utilizadas; hoy me tomo el tiempo de repasar 17 características importantes, aunque desde luego hay más allá de estas.

1. Algoritmos paralelos

C++17 introdujo versiones paralelas de muchos de los algoritmos de la biblioteca estándar. Estos algoritmos pueden ejecutarse en paralelo, por lo que en sistemas multinúcleo pueden aportar mejoras de rendimiento notables.

Antes escribí un artículo que presenta esta característica a fondo; puedes consultarlo aquí:

Ejemplo:

#include <algorithm>
#include <vector>
#include <execution>
int main() {
    std::vector<int> v = {1, 2, 3, 4, 5};
    std::sort(std::execution::par, v.begin(), v.end());
}

En este ejemplo, std::sort se ejecuta en paralelo, ordenando los elementos del vector v de manera paralela.

2. If Initializers

El inicializador de if de C++17 es una característica que permite inicializar variables directamente en la sentencia if. Esta forma de inicialización puede mejorar en cierto modo la legibilidad y la concisión del código.

En el C++ tradicional, normalmente inicializaríamos la variable así:

int x;
if (condition) {
    x = 42;
} else {
    x = 24;
}

En C++17, en cambio, se puede usar el inicializador de if para simplificar el proceso y hacer el código más compacto:

if (bool condition = /* some condition */) {
    int x = 42;
} else {
    int x = 24;
}

En este ejemplo, colocamos la inicialización de la variable x directamente en la sentencia if. La variable condition se define e inicializa en la sentencia if y queda disponible dentro del bloque if. Este enfoque es más intuitivo y conciso, sobre todo en inicializaciones condicionales sencillas.

3. Deducción de argumentos de plantilla de clase (CTAD)

CTAD permite que el compilador deduzca automáticamente los parámetros de plantilla a partir de los argumentos de la función. Esto facilita el uso de plantillas sin tener que especificar los parámetros de plantilla de forma explícita.

En una entrega anterior ya expliqué esta característica a fondo:

Ejemplo:

template <typename T>
void foo(T t) {
    // ...
}

int main() {
    foo(42);  // 编译器推导出T的类型为int
}

En este ejemplo, al llamar a foo(42), el compilador deduce que el tipo de T es int.

4. template <auto>

La palabra clave de plantilla se introdujo como marcador para parámetros de plantilla no tipo. Permite representar en las plantillas valores de cualquier tipo.

Ejemplo:

template<auto value>
struct constant {
    static constexpr auto get_value() { return value; }
};

// 用法
static_assert(constant<42>::get_value() == 42);

5. std::optional y std::variant

std::optional y std::variant son dos nuevos tipos introducidos en C++17. std::optional representa un valor que puede existir o no, y std::variant representa una unión con seguridad de tipos, capaz de almacenar valores de distintos tipos.

Ejemplo:

#include <optional>
#include <variant>

int main() {
    std::optional<int> opt = 42;
    std::variant<int, double> var = 3.14;
}

En este ejemplo, opt es un entero opcional que contiene el valor 42, y var es un variant que contiene el valor 3.14.

6. Expresiones de pliegue (fold expressions)

En C++17, las expresiones de pliegue ofrecen una forma concisa de aplicar una operación binaria sobre un paquete de parámetros. Permiten ejecutar, sin recursión o iteración explícita, operaciones como sumar, multiplicar o concatenar los elementos de un paquete de parámetros.

Por ejemplo:

#include <iostream>

template<typename T>
T sum(T t) {
    return t;
}

// 使用折叠表达式的递归情况
template<typename T, typename... Args>
T sum(T first, Args... args) {
    return first + sum(args...);
}

int main() {
    int total = sum(1, 2, 3, 4, 5);
    std::cout << "总和: " << total << std::endl;

    return 0;
}

La expresión de pliegue (first + ... + args) de la función sum recursiva aplica la operación de suma a cada elemento del paquete de parámetros.

7. Vinculaciones estructuradas

Las vinculaciones estructuradas permiten descomponer un objeto en sus elementos constituyentes, de forma parecida al desempaquetado de tuplas que quizá ya conozcas.

Artículo anterior:

Ejemplo:

#include <tuple>
#include <string>
int main() {
    std::tuple<int, std::string, double> t(42, "hello", 3.14);
    auto [i, s, d] = t;  // i = 42, s = "hello", d = 3.14
}

En este ejemplo, la vinculación estructurada [i, s, d] descompone la tupla t en sus elementos constituyentes.

8. Parámetros de plantilla de plantilla

Por ejemplo: en C++17, la sintaxis template<template<class...>typename bob> struct foo {} declara una plantilla llamada foo que acepta un parámetro de plantilla de plantilla llamado bob. El parámetro de plantilla de plantilla bob acepta a su vez cualquier número de parámetros de tipo de plantilla.

template <template<class...> typename bob>
struct foo {
    template<typename T>
    void bar(const bob<T>& arg) {
        std::cout << "size: " << arg.size() << std::endl;
    }
};

int main() {
    foo<std::vector> f;
    std::vector<int> vec = {1, 2, 3, 4, 5};
    f.bar(vec);

    return 0;
}

En la función main, instanciamos foo con std::vector, pasándolo como parámetro de plantilla de bob. Esto nos permite crear una estructura genérica foo que puede trabajar con cualquier plantilla que acepte un número arbitrario de parámetros de tipo, como std::vector, std::list o plantillas definidas por el usuario.

9. Variables inline

C++17 permite definir variables como inline dentro de la definición de una clase, lo que puede ayudar a reducir el tamaño del binario y posiblemente mejorar el rendimiento, al evitar copias duplicadas de la variable en múltiples unidades de traducción.

Ejemplo:

class MyClass {
public:
    inline static int inlineVar = 42;
};

int main() {
    int localVar = MyClass::inlineVar;
}

Aquí, inlineVar es la variable miembro estática inline de MyClass.

10. Mejoras en los atributos

C++17 proporciona atributos nuevos y mejora los ya existentes, permitiendo a quien desarrolla dar al compilador más información sobre el comportamiento del código.

Ejemplo:

[[nodiscard]] int get_sum(int a, int b) {
    return a + b;
}

int main() {
    auto result = get_sum(1, 2);
    // 编译器可能会警告‘result’未使用
}

En este ejemplo, [[nodiscard]] es un atributo que puede aplicarse a una función e indica que su valor de retorno no debería descartarse en la llamada.

11. Espacios de nombres anidados

C++17 mejoró las plantillas variádicas mediante expresiones de pliegue, haciendo el código más conciso y expresivo al manejar paquetes de parámetros.

Ejemplo:

// 外部命名空间
namespace outer {
    // 内部命名空间
    namespace inner {
        void foo() {
            std::cout << "在内部命名空间中" << std::endl;
        }
    }
}

outer::inner::foo();

Las definiciones de espacios de nombres anidados ofrecen una forma de organizar el código jerárquicamente, mejorando la legibilidad y la mantenibilidad, especialmente en proyectos grandes. Además, al proporcionar una jerarquía de espacios de nombres más estructurada, ayudan a evitar conflictos de nombres.

12. Mejoras en los literales

C++17 mejoró los literales, incluyendo mejoras en los literales enteros y de coma flotante, así como soporte para los literales true y false.

Ejemplo:

auto num = 123_456; // Underscore in integer literals  
auto pi = 3.1415_f; // Suffix for floating-point literals

13. constexpr Lambda

C++17 permite que las funciones lambda sean constexpr; si cumplen los requisitos, pueden usarse en contextos que exigen evaluación en tiempo de compilación, por ejemplo:

constexpr auto lambda = [](int x) { return x * 2; };
static_assert(lambda(5) == 10);

En este ejemplo, lambda es una lambda constexpr que toma como parámetro un entero x y devuelve el doble de x. El static_assert comprueba en tiempo de compilación si el valor de lambda(5) es igual a 10.

14. Captura de *this

En las lambdas, capturar *this se volvió más sencillo, permitiendo el acceso directo a los miembros del objeto contenedor.

class Foo {
    int data = 42;
public:
    auto member_lambda() {
        return [*this] { std::cout << data << std::endl; };
    }
};

// 使用
Foo f;
f.member_lambda()(); // 输出 "42"

15. Sentencias if y switch extendidas

La condición de una sentencia if o switch ahora puede ser cualquier expresión, sin limitarse a condiciones booleanas. Esto hace el flujo de control más flexible, por ejemplo al usar vinculaciones estructuradas:

if (const auto [it, inserted] = map.insert({"foo", bar}); inserted) {
    // ...
}

En este ejemplo, la sentencia if comprueba si la variable inserted es verdadera, pero la condición incluye también la asignación de la vinculación estructurada.

16. Bucle for basado en rangos generalizado

Esta mejora admite centinelas o iteradores de fin con un tipo distinto del iterador de inicio, lo que ayuda a tratar bucles terminados en nulo y otros casos parecidos. Por ejemplo:

for (auto it = my_container.begin(); it != my_container.end(); ++it) {
   // ...
}

En este ejemplo, my_container podría ser un contenedor que usa un tipo distinto de iterador de fin, pero el bucle seguiría funcionando correctamente.

17. if constexpr

Esta característica permite al compilador evaluar la condición en tiempo de compilación, posibilitando código más genérico. Si la condición es verdadera, el código compilado incluye el contenido del bloque if; en caso contrario, se descarta. Esto puede simplificar el código eliminando en tiempo de ejecución ramas innecesarias. Por ejemplo:

if constexpr (std::is_same_v<T, int>) {
   // 用于int的特定代码
} else {
   // 用于其他类型的代码
}

En este ejemplo, la sentencia if constexpr comprueba si el tipo T es int e incluye en consecuencia el código apropiado.

Resumen

Por ahora lo dejamos aquí; el resto del contenido se explicará en la próxima sección.

Temas relacionados

Comentarios