Mejoras de conformidad de C++, cambios de comportamiento y correcciones de errores en Visual Studio 2019

Microsoft C/C++ en Visual Studio (MSVC) hace mejoras de conformidad y corrige errores en cada versión. En este artículo se enumeran las mejoras por versión principal y luego por versión. Para saltar directamente a los cambios para una versión específica, use la lista a continuación de En este artículo.

En este documento se enumeran los cambios en Visual Studio 2019. Para acceder a una guía de los cambios en Visual Studio 2022, consulte Mejoras de conformidad de C++ en Visual Studio 2022. Para conocer los cambios en Visual Studio 2017, consulte Mejoras de conformidad de C++ en Visual Studio 2017. Para obtener una lista completa de las mejoras de conformidad anteriores, vea Novedades de Visual C++ de 2003 a 2015.

Mejoras de conformidad en Visual Studio 2019 RTW (versión 16.0)

Visual Studio 2019 RTW contiene las siguientes mejoras de conformidad, correcciones de errores y cambios de comportamiento del compilador de Microsoft C++.

Nota

Las características de C++20 solo estaban disponibles en modo /std:c++latest en Visual Studio 2019 hasta que la implementación de C++20 se consideró completa. La versión 16.11 de Visual Studio 2019 presenta el modo de compilador /std:c++20. En este artículo, las características que originalmente requerían el modo /std:c++latest ahora funcionan en el modo /std:c++20 o posterior en las versiones más recientes de Visual Studio. Hemos actualizado la documentación para mencionar /std:c++20 , aunque esta opción no estaba disponible cuando se publicaron las características por primera vez.

Mejor compatibilidad de módulos en las plantillas y para la detección de errores

Ahora, los módulos están oficialmente en el estándar C++20. Se ha agregado mejor compatibilidad en la versión 15.9 de Visual Studio 2017. Para más información, vea Better template support and error detection in C++ Modules with MSVC 2017 version 15.9 (Mejor compatibilidad de plantilla y detección de errores en los módulos de C++ con MSVC 2017 versión 15.9).

Especificación modificada de tipo de agregado

La especificación de un tipo agregado ha cambiado en C++20; vea Prohibit aggregates with user-declared constructors (Prohibir agregados con constructores declarados por el usuario). En Visual Studio 2019, en /std:c++latest (o /std:c++20 en Visual Studio 2019, versión 16.11 y posteriores), una clase con cualquier constructor declarado por el usuario (por ejemplo, que incluya un constructor declarado = default o = delete) no constituye un agregado. Antes, solo los constructores proporcionados por el usuario podían hacer que una clase no se cualificara como un agregado. Este cambio impone más restricciones sobre cómo se pueden inicializar estos tipos.

El siguiente código se compila sin errores en Visual Studio 2017, pero genera los errores C2280 y C2440 en el modo /std:c++20 o /std:c++latest en Visual Studio 2019:

struct A
{
    A() = delete; // user-declared ctor
};

struct B
{
    B() = default; // user-declared ctor
    int i = 0;
};

A a{}; // ill-formed in C++20, previously well-formed
B b = { 1 }; // ill-formed in C++20, previously well-formed

Compatibilidad parcial para operator <=>

P0515R3C++20 presenta el operador de comparación a tres sentidos <=>, también conocido como "operador de nave espacial". El modo /std:c++latest de la versión 16.0 de Visual Studio 2019 tiene compatibilidad parcial con el operador al generar errores por usar una sintaxis que ahora no está permitida. Por ejemplo, el siguiente código se compila sin errores en Visual Studio 2017, pero genera varios errores en el modo /std:c++20 o /std:c++latest en Visual Studio 2019:

struct S
{
    bool operator<=(const S&) const { return true; }
};

template <bool (S::*)(const S&) const>
struct U { };

int main(int argc, char** argv)
{
    U<&S::operator<=> u; // In Visual Studio 2019 raises C2039, 2065, 2146.
}

Para evitar estos errores, inserte un espacio en la línea incorrecta, antes del corchete angular: U<&S::operator<= > u;.

Referencias a tipos con calificadores CV discrepantes

Nota:

Este cambio solo afecta a las versiones 16.0 a 16.8 de Visual Studio 2019. Se revirtió a partir de la versión 16.9 de Visual Studio 2019.

Antes, MSVC permitía el enlace directo de una referencia de un tipo con calificadores CV discrepantes por debajo del nivel superior. Este enlace podía permitir la modificación de los datos supuestamente constantes a los que hace referencia la referencia.

En su lugar, el compilador para las versiones 16.0 a 16.8 de Visual Studio 2019, crea un valor temporal, tal como lo requería el estándar en ese momento. Más adelante, el estándar cambió retroactivamente para que el comportamiento anterior de Visual Studio 2017 y versiones anteriores fuera correcto, pero el comportamiento de las versiones 16.0 a 16.8 de Visual Studio 2019 no es correcto. Por lo tanto, este cambio se revirtió a partir de la versión 16.9 de Visual Studio 2019.

Vea Tipos y enlaces de referencia similares para consultar un cambio relacionado.

Por ejemplo, en Visual Studio 2017, el código siguiente se compila sin advertencias. En las versiones 16.0 a 16.8 de Visual Studio 2019, el compilador genera la advertencia C4172. A partir de La versión 16.9 de Visual Studio 2019, el código se vuelve a compilar sin advertencias:

struct X
{
    const void* const& PData() const
    {
        return _pv;
    }

    void* _pv;
};

int main()
{
    X x;
    auto p = x.PData(); // C4172 <func:#1 "?PData@X@@QBEABQBXXZ"> returning address of local variable or temporary
}

reinterpret_cast de una función sobrecargada

El argumento para reinterpret_cast no es uno de los contextos en los que se permite la dirección de una función sobrecargada. El siguiente código se compila sin errores en Visual Studio 2017, pero en Visual Studio 2019 genera correctamente el error C2440:

int f(int) { return 1; }
int f(float) { return .1f; }
using fp = int(*)(int);

int main()
{
    fp r = reinterpret_cast<fp>(&f); // C2440: cannot convert from 'overloaded-function' to 'fp'
}

Para evitar el error, use una conversión permitida en este escenario:

int f(int);
int f(float);
using fp = int(*)(int);

int main()
{
    fp r = static_cast<fp>(&f); // or just &f;
}

Cierres de lambda

En C++14, los tipos de cierre de lambda no son literales. La principal consecuencia de esta regla es que una expresión lambda no se puede asignar a una variable constexpr. El siguiente código se compila sin errores en Visual Studio 2017, pero en Visual Studio 2019 genera correctamente el error C2127:

int main()
{
    constexpr auto l = [] {}; // C2127 'l': illegal initialization of 'constexpr' entity with a non-constant expression
}

Para evitar el error, quite el calificador constexpr o cambie el modo de conformidad a /std:c++17 o posterior.

Códigos de error std::create_directory

P1164 implementado de C ++ 20 sin condiciones. Esto cambia std::create_directory en el sentido de que se comprueba si el destino ya es un directorio en caso de error. Antes, todos los errores de tipo ERROR_ALREADY_EXISTS se convertían en códigos que se ejecutaban correctamente, pero que no creaban un directorio.

operator<<(std::ostream, nullptr_t)

Según LWG 2221, se ha agregado operator<<(std::ostream, nullptr_t) para escribir nullptr en secuencias.

Más algoritmos paralelos

Nuevas versiones en paralelo de is_sorted, is_sorted_until, is_partitioned, set_difference, set_intersection, is_heap y is_heap_until.

Correcciones en la inicialización atómica

En el documento P0883 sobre fijación de la inicialización atómica se cambia std::atomic de forma que el elemento T contenido se inicializa según su valor T, y no de manera predeterminada. Esta corrección se habilita cuando se usa Clang/LLVM con la biblioteca estándar de Microsoft. Actualmente está deshabilitada en el compilador de Microsoft C++ como solución provisional de un error en el procesamiento de constexpr.

remove_cvref y remove_cvref_t

Se han implementado los rasgos de tipos remove_cvref y remove_cvref_t según el documento P0550. Lo que hacen es quitar la capacidad de referencia y la cualificación CV de un tipo sin que las funciones y matrices a punteros decaigan (a diferencia de std::decay y std::decay_t).

Macros para la prueba de características

La característica Macros de prueba de características (P0941R2) está completa, con compatibilidad con __has_cpp_attribute. Ahora, las macros de prueba de características se admiten en todos los modos estándares.

Prohibición de agregados con constructores declarados por el usuario

El documento P1008R1 de C++20 sobre la prohibición de agregados con constructores declarados por el usuario está completo.

reinterpret_cast en una función constexpr

Un reinterpret_cast no es válido en una función constexpr . El compilador de Microsoft C++ anteriormente rechazaba reinterpret_cast solo si se usaba en un contexto de constexpr . En Visual Studio 2019, en todos los modos estándar de lenguaje, el compilador diagnostica correctamente reinterpret_cast en la definición de una función constexpr . El código siguiente genera ahora el error C3615:

long long i = 0;
constexpr void f() {
    int* a = reinterpret_cast<int*>(i); // C3615: constexpr function 'f' cannot result in a constant expression
}

Para evitar el error, quite el modificador constexpr de la declaración de función.

Diagnóstico correcto del constructor de rango de basic_string

En Visual Studio 2019, el constructor de rango basic_string ya no suprime los diagnósticos de compilador con static_cast. El siguiente código se compila sin advertencias en Visual Studio 2017, a pesar de la posible pérdida de datos de wchar_t a char al inicializar out:

std::wstring ws = /* . . . */;
std::string out(ws.begin(), ws.end()); // VS2019 C4244: 'argument': conversion from 'wchar_t' to 'const _Elem', possible loss of data.

Visual Studio 2019 genera correctamente la advertencia C4244. Para evitar la advertencia, puede inicializar std::string como se muestra en este ejemplo:

std::wstring ws = L"Hello world";
std::string out;
for (wchar_t ch : ws)
{
    out.push_back(static_cast<char>(ch));
}

Ahora las llamadas incorrectas a += y -= bajo /clr o /ZW se detectan correctamente

En Visual Studio 2017 se producía un error que hacía que el compilador ignorara los errores de manera inadvertida y no generara ningún código para las llamadas no válidas a += y -= bajo /clr o /ZW . El siguiente código se compila sin errores en Visual Studio 2017, pero en Visual Studio 2019 genera correctamente el error C2845:

public enum class E { e };

void f(System::String ^s)
{
    s += E::e; // in VS2019 C2845: 'System::String ^': pointer arithmetic not allowed on this type.
}

Para evitar el error de este ejemplo, use el operador += con el método ToString(): s += E::e.ToString();.

Inicializadores de miembros de datos estáticos en línea

Los accesos de miembros no válidos dentro de los inicializadores inline y static constexpr ahora se detectan correctamente. El ejemplo siguiente se compila sin errores en Visual Studio 2017, pero en Visual Studio 2019 en el modo /std:c++17 o posterior se genera el error C2248:

struct X
{
    private:
        static inline const int c = 1000;
};

struct Y : X
{
    static inline int d = c; // VS2019 C2248: cannot access private member declared in class 'X'.
};

Para evitar el error, declare el miembro X::c como protegido:

struct X
{
    protected:
        static inline const int c = 1000;
};

C4800 restablecido

MSVC solía tener una advertencia de rendimiento C4800 sobre la conversión implícita a bool. Era demasiado ruidosa y no se pudo eliminar, lo que nos llevó a quitarla en Visual Studio 2017. Sin embargo, durante el ciclo de vida de Visual Studio 2017 recibimos muchos comentarios sobre lo útil que era para resolver ciertos casos. En Visual Studio 2019 volvemos a incorporar una advertencia C4800 cuidadosamente diseñada, junto con la advertencia explicativa C4165. Ambas advertencias son fáciles de suprimir: mediante el uso de una conversión explícita o por comparación con 0 del tipo adecuado. C4800 es una advertencia de nivel 4 desactivada de forma predeterminada y C4165, una advertencia de nivel 3 también desactivada de forma predeterminada. Ambas pueden detectarse con la opción de compilador /Wall.

El siguiente ejemplo genera las advertencias C4800 y C4165 en /Wall:

bool test(IUnknown* p)
{
    bool valid = p; // warning C4800: Implicit conversion from 'IUnknown*' to bool. Possible information loss
    IDispatch* d = nullptr;
    HRESULT hr = p->QueryInterface(__uuidof(IDispatch), reinterpret_cast<void**>(&d));
    return hr; // warning C4165: 'HRESULT' is being converted to 'bool'; are you sure this is what you want?
}

Para evitar las advertencias en el ejemplo anterior, puede escribir un código similar al siguiente:

bool test(IUnknown* p)
{
    bool valid = p != nullptr; // OK
    IDispatch* d = nullptr;
    HRESULT hr = p->QueryInterface(__uuidof(IDispatch), reinterpret_cast<void**>(&d));
    return SUCCEEDED(hr);  // OK
}

La función miembro de clase local no tiene cuerpo

En Visual Studio 2017, la advertencia C4822 solo se genera cuando la opción del compilador /w14822 se establece explícitamente. No se muestra con /Wall. En Visual Studio 2019, C4822 es una advertencia desactivada forma predeterminada que hace que sea detectable en /Wall sin tener que establecer /w14822 explícitamente.

void example()
{
    struct A
        {
            int boo(); // warning C4822: Local class member function doesn't have a body
        };
}

Cuerpos de plantilla de función que contienen instrucciones if constexpr

En Visual Studio 2019, en /std:c++20 o /std:c++latest , los cuerpos de función de plantilla que incluyen instrucciones if constexpr tienen habilitadas comprobaciones adicionales relacionadas con el análisis sintáctico. Por ejemplo, en Visual Studio 2017, el siguiente código genera el error C7510 solo si se establece la opción /permissive-. En Visual Studio 2019, el mismo código genera errores incluso cuando la opción /permissive está establecida:

// C7510.cpp
// compile using: cl /EHsc /W4 /permissive /std:c++latest C7510.cpp
#include <iostream>

template <typename T>
int f()
{
    T::Type a; // error C7510: 'Type': use of dependent type name must be prefixed with 'typename'
    // To fix the error, add the 'typename' keyword. Use this declaration instead:
    // typename T::Type a;

    if constexpr (a.val)
    {
        return 1;
    }
    else
    {
        return 2;
    }
}

struct X
{
    using Type = X;
    constexpr static int val = 1;
};

int main()
{
    std::cout << f<X>() << "\n";
}

Para evitar el error, agregue la palabra clave typename a la declaración de a: typename T::Type a;.

El código de ensamblado en línea no se admite en una expresión lambda

Recientemente se puso en conocimiento del equipo de Microsoft C++ un problema de seguridad por el cual el uso de un ensamblador en línea en una expresión lambda podía provocar daños en ebp (el registro de remites) en tiempo de ejecución. Un atacante malintencionado podría sacar provecho de este escenario. El ensamblador alineado solo se admite en x86 y la interacción entre el ensamblador insertado y el resto del compilador es deficiente. Dados estos hechos y la naturaleza del problema, la solución más segura para este problema era impedir el ensamblador insertado en una expresión lambda.

El único uso del ensamblador en línea dentro de una expresión lambda que hemos encontrado de facto fue capturar el remite. En este escenario, se pueden capturar los remites de todas las plataformas utilizando tan solo una función intrínseca de compilador _ReturnAddress().

El código siguiente produce C7553 en Visual Studio 2017 15.9 y versiones posteriores de Visual Studio:

#include <cstdio>

int f()
{
    int y = 1724;
    int x = 0xdeadbeef;

    auto lambda = [&]
    {
        __asm {  // C7553: inline assembler is not supported in a lambda

            mov eax, x
            mov y, eax
        }
    };

    lambda();
    return y;
}

Para evitar el error, mueva el código del ensamblado a una función con nombre, tal como se muestra en el ejemplo siguiente:

#include <cstdio>

void g(int& x, int& y)
{
    __asm {
        mov eax, x
        mov y, eax
    }
}

int f()
{
    int y = 1724;
    int x = 0xdeadbeef;
    auto lambda = [&]
    {
        g(x, y);
    };
    lambda();
    return y;
}

int main()
{
    std::printf("%d\n", f());
}

Depuración de iterador y std::move_iterator

La característica de depuración de iterador se ha diseñado para desajustar std::move_iterator correctamente. Por ejemplo, std::copy(std::move_iterator<std::vector<int>::iterator>, std::move_iterator<std::vector<int>::iterator>, int*) ahora puede participar en el método rápido memcpy.

Correcciones para la aplicación de la palabra clave <xkeycheck.h>

Se ha corregido la aplicación de la biblioteca estándar en <xkeycheck.h> para las macros que reemplazan una palabra clave. La biblioteca ahora emite la palabra clave del problema real que se detectó en lugar de un mensaje genérico. También admite palabras clave de C++20 y evita la falsa detección de IntelliSense de palabras clave aleatorias como macros.

Tipos de asignador que ya no está en desuso

std::allocator<void>, std::allocator::size_type y std::allocator::difference_type ya no están en desuso.

Advertencia correcta para restringir conversiones de cadena

Se ha quitado un elemento falso static_cast de std::string no convocado por el estándar y que suprimía por error las advertencias de restricción de C4244. Los intentos de llamar a std::string::string(const wchar_t*, const wchar_t*) ahora emiten correctamente C4244 sobre la restricción de wchar_t en char.

Varias correcciones de exactitud de <filesystem>

  • Se ha corregido el error std::filesystem::last_write_time que se producía al intentar cambiar la última hora de escritura de un directorio.
  • Ahora, el constructor std::filesystem::directory_entry almacena un resultado erróneo (en lugar de producir una excepción) si se indica una ruta de acceso de destino que no existe.
  • La versión de 2 parámetros std::filesystem::create_directory se ha cambiado para llamar a la versión de 1 parámetro, ya que la función CreateDirectoryExW subyacente usaría copy_symlink si existing_p fuera un vínculo simbólico.
  • std::filesystem::directory_iterator ya no genera un error al encontrar un vínculo simbólico roto.
  • Ahora, std::filesystem::space acepta rutas de acceso relativas.
  • std::filesystem::path::lexically_relative ya no se confunde ante la presencia de barras diagonales finales, notificadas como LWG 3096.
  • Se ha solucionado el método CreateSymbolicLinkW, que rechazaba las rutas de acceso con barras diagonales en std::filesystem::create_symlink.
  • Se ha solucionado la función delete de modo de eliminación de POSIX que existía en Windows 10 LTSB 1609, pero en realidad los archivos no se pueden eliminar.
  • Ahora, los constructores de copia de std::boyer_moore_searcher y std::boyer_moore_horspool_searcher y los operadores de asignación de copia copian de verdad.

Algoritmos paralelos en Windows 8 y versiones posteriores

Ahora, la biblioteca de algoritmos paralelos usa correctamente la familia WaitOnAddress real en Windows 8 y en versiones posteriores, en lugar de usar siempre Windows 7 y versiones anteriores falsas.

Espacio en blanco std::system_category::message()

Ahora, std::system_category::message() recorta los espacios en blanco finales en el mensaje devuelto.

Dividir por cero std::linear_congruential_engine

Se han corregido algunas situaciones en las que std::linear_congruential_engine desencadenaría una división entre 0.

Correcciones de desajuste de iteradores

Alguna maquinaria de desajuste de iteradores que se expuso por primera vez para la integración programador-usuario en la versión 15.8 de Visual Studio 2017. Se describió en el artículo del blog del equipo de C++ Correcciones y características de STL en VS 2017 15.8. Esta maquinaria ya no desencapsula los iteradores derivados de los iteradores de la biblioteca estándar. Por ejemplo, un usuario que deriva de std::vector<int>::iterator e intenta personalizar el comportamiento, ahora obtendrá su comportamiento personalizado cuando llame a algoritmos de la biblioteca estándar, y no el comportamiento de un puntero.

Ahora, la función reserve de contenedor sin ordenar reserva de verdad para N elementos, como se describe en LWG 2156.

Control del tiempo

  • Antes, algunos valores de tiempo que pasaban a la biblioteca de simultaneidad podían desbordarse, por ejemplo, condition_variable::wait_for(seconds::max()). Ahora corregidos, los desbordamientos cambiaban el comportamiento en un ciclo aparentemente aleatorio de 29 días (cuando Win32 subyacente aceptaba uint32_t milisegundos, las API se desbordaban).

  • Ahora, el encabezado <ctime> declara correctamente timespec y timespec_get en el espacio de nombres std, y también en el espacio de nombres global.

Varias correcciones de contenedores

  • Muchas de las funciones de contenedor internas de la biblioteca estándar se han convertido en private para una mejor experiencia de IntelliSense. Se espera realizar más correcciones para marcar miembros como private en las versiones posteriores de MSVC.

  • Se han corregido problemas de corrección de seguridad de excepciones que provocaban daños en los contenedores basados en nodos, como list, map y unordered_map. Durante una operación de reasignación propagate_on_container_copy_assignment o propagate_on_container_move_assignment, liberaríamos el nodo centinela del contenedor con el asignador anterior, realizaríamos la asignación de POCCA/POCMA sobre el asignador anterior y, a continuación, intentaríamos adquirir el nodo centinela del nuevo asignador. Si se produjo un error en esta asignación, es porque el contenedor estaba dañado. Incluso no se podía destruir, ya que la propiedad de un nodo centinela es una invariable de estructura de datos rígida. Este código se corrigió para asignar el nodo centinela nuevo mediante el asignador del contenedor de origen antes de destruir el nodo centinela existente.

  • Los contenedores se han corregido de forma que los asignadores siempre se copian/mueven/intercambian según propagate_on_container_copy_assignment, propagate_on_container_move_assignment y propagate_on_container_swap, incluso en el caso de los asignadores declarados como is_always_equal.

  • Se han agregado sobrecargas para las funciones miembro de extracción y combinación de contenedores que aceptan contenedores rvalue. Para más información, consulte P0083 sobre la inserción e asignaciones y conjuntos.

std::basic_istream::read procesamiento de \r\n`` =>\n`

std::basic_istream::read se ha corregido para que no escriba en partes del búfer proporcionado temporalmente como parte del procesamiento de \r\n a \n. Este cambio cede parte de la ventaja de rendimiento que se obtuvo en Visual Studio 2017 15.8 para lecturas con un tamaño superior a 4K. Sin embargo, las mejoras en la eficiencia de evitar tres llamadas virtuales por carácter todavía están presentes.

Constructor std::bitset

El constructor std::bitset ya no lee los unos y los ceros en orden inverso en los conjuntos de bits grandes.

Regresión std::pair::operator=

Se ha corregido una regresión en el operador de asignación std::pair que se producía al implementar LWG 2729 "Missing SFINAE en std::pair::operator=";. Ahora, se vuelven a aceptar correctamente los tipos convertibles a std::pair.

Contextos no deducidos para add_const_t

Se ha corregido un error menor de rasgos de tipos, donde se supone que add_const_t y funciones relacionadas están un contexto no deducido. En otras palabras, add_const_t debe ser un alias para typename add_const<T>::type, no const T.

Mejoras de conformidad en la versión 16.1

char8_t

P0482r6. C++20 agrega un nuevo tipo de carácter que se utiliza para representar unidades de código UTF-8. Los literales de cadena u8 en C++20 tienen un tipo const char8_t[N] en lugar de const char[N] que era lo que sucedía antes. Se han propuesto cambios similares para el estándar de C en N2231. En P1423r3 se proporcionan sugerencias para la corrección de compatibilidad con versiones anteriores de char8_t. El compilador de Microsoft C++ agrega compatibilidad con char8_t en Visual Studio 2019 versión 16.1 cuando se especifica la opción de compilador /Zc:char8_t . Se puede revertir al comportamiento de C++17 a través de /Zc:char8_t- . El compilador EDG que impulsa IntelliSense aún no lo admite en Visual Studio 2019, versión 16.1. Es posible que vea errores falsos solo relativos a IntelliSense que no afectan a la compilación real.

Ejemplo

const char* s = u8"Hello"; // C++17
const char8_t* s = u8"Hello"; // C++20

La metafunción std::type_identity y el objeto de función std::identity

P0887R1 type_identity. La extensión de la plantilla de clase std::identity en desuso se ha quitado y se ha reemplazado por la metafunción std::type_identity y el objeto de función std::identity de C++20. Ambos disponibles solo en /std:c++latest (/std:c++20 en Visual Studio 2019, versión 16.11 y posteriores).

En el ejemplo siguiente se genera una advertencia C4996 de elemento en desuso relativa al objeto std::identity (definido en <type_traits>) en Visual Studio 2017:

#include <type_traits>

using T = std::identity<int>::type;
T x, y = std::identity<T>{}(x);
int i = 42;
long j = std::identity<long>{}(i);

En el ejemplo siguiente se muestra cómo usar el nuevo objeto std::identity (definido en <functional>) junto con la nueva std::type_identity:

#include <type_traits>
#include <functional>

using T = std::type_identity<int>::type;
T x, y = std::identity{}(x);
int i = 42;
long j = static_cast<long>(i);

Comprobaciones de sintaxis en lambdas genéricas

El nuevo procesador lambda permite algunas comprobaciones sintácticas en modo de conformidad en lambdas genéricas, en /std:c++latest (/std:c++20 en Visual Studio 2019, versión 16.11 y posteriores) o en cualquier otro modo de lenguaje con /Zc:lambda en Visual Studio 2019, versión 16.9 o posteriores (disponible anteriormente como /experimental:newLambdaProcessor a partir de Visual Studio 2019, versión 16.3).

El procesador lambda heredado compila este ejemplo sin advertencias, pero el nuevo procesador lambda genera el error C2760:

void f() {
    auto a = [](auto arg) {
        decltype(arg)::Type t; // C2760 syntax error: unexpected token 'identifier', expected ';'
    };
}

En el siguiente ejemplo se muestra la sintaxis correcta, que ahora aplica el compilador:

void f() {
    auto a = [](auto arg) {
        typename decltype(arg)::Type t;
    };
}

Búsqueda dependiente de argumentos en llamadas de función

P0846R0 (C++20) Se ha aumentado la capacidad de encontrar plantillas de función a través de la búsqueda dependiente de argumentos en expresiones de llamada de función con argumentos de plantilla explícitos. Requiere /std:c++latest (o /std:c++20 en Visual Studio 2019, versión 16.11 y posteriores).

Inicialización designada

P0329R4 (C++20) La inicialización designada permite seleccionar miembros específicos en la inicialización agregada usando la sintaxis Type t { .member = expr }. Requiere /std:c++latest (o /std:c++20 en Visual Studio 2019, versión 16.11 y posteriores).

Clasificación de la conversión de enumeración a su tipo subyacente fijo

El compilador ahora clasifica las conversiones de enumeración según las secuencias de conversión implícitas de clasificación N4800 11.3.3.2 (4.2):

  • Una conversión que promueve una enumeración cuyo tipo subyacente se fija en su tipo subyacente es mejor que una que promueve al tipo subyacente promocionado, si son diferentes.

Esta clasificación de conversión no se implementó correctamente hasta la versión 16.1 de Visual Studio 2019. El comportamiento conforme puede cambiar el comportamiento de resolución de sobrecarga o exponer una ambigüedad donde no se había detectado ninguna anteriormente.

Este cambio de comportamiento del compilador se aplica a todos los modos /std y es un cambio importante tanto de origen como binario.

En el ejemplo siguiente se muestra cómo cambia el comportamiento del compilador en la versión 16.1 y posteriores:

#include <type_traits>

enum E : unsigned char { e };

int f(unsigned int)
{
    return 1;
}

int f(unsigned char)
{
    return 2;
}

struct A {};
struct B : public A {};

int f(unsigned int, const B&)
{
    return 3;
}

int f(unsigned char, const A&)
{
    return 4;
}

int main()
{
    // Calls f(unsigned char) in 16.1 and later. Called f(unsigned int) in earlier versions.
    // The conversion from 'E' to the fixed underlying type 'unsigned char' is better than the
    // conversion from 'E' to the promoted type 'unsigned int'.
    f(e);
  
    // Error C2666. This call is ambiguous, but previously called f(unsigned int, const B&). 
    f(e, B{});
}

Funciones de la biblioteca estándar nuevas y actualizadas (C++20)

  • starts_with() y ends_with() para basic_string y basic_string_view.
  • contains() para contenedores asociativos.
  • remove(), remove_if() y unique() para list y forward_list ahora devuelven size_type.
  • Se han agregado shift_left() y shift_right() a <algoritmo>.

Mejoras de conformidad en la versión 16.2

noexceptFunciones constexpr

Las funciones constexpr ya no se consideran noexcept de manera predeterminada cuando se usan en una expresión constante. Este cambio de comportamiento procede de la resolución del grupo de trabajo principal (CWG) CWG 1351 y está habilitado en /permissive-. El ejemplo siguiente se compila en la versión 16.1 de Visual Studio 2019 y en versiones anteriores, pero se genera C2338 en la versión 16.2 de Visual Studio 2019:

constexpr int f() { return 0; }

int main() {
    static_assert(noexcept(f()), "f should be noexcept"); // C2338 in 16.2
}

Para corregir el error, agregue la expresión noexcept a la declaración de la función:

constexpr int f() noexcept { return 0; }

int main() {
    static_assert(noexcept(f()), "f should be noexcept");
}

Expresiones binarias con distintos tipos de enumeración

C++20 ha dejado en desuso las conversiones aritméticas usuales en los operandos, donde:

  • Un operando es de tipo de enumeración.

  • El otro es de un tipo de enumeración distinto o de un tipo de punto flotante.

Para más información, consulte P1120R0.

En Visual Studio 2019, versión 16.2 y posteriores, el código siguiente produce una advertencia C5054 de nivel 4 cuando la opción del compilador /std:c++latest está habilitada (/std:c++20 en Visual Studio 2019, versión 16.11 y posteriores):

enum E1 { a };
enum E2 { b };
int main() {
    int i = a | b; // warning C5054: operator '|': deprecated between enumerations of different types
}

Para evitar la advertencia, use static_cast para convertir el segundo operando:

enum E1 { a };
enum E2 { b };
int main() {
  int i = a | static_cast<int>(b);
}

El uso de una operación binaria entre un tipo de enumeración y un tipo de punto flotante ahora es una advertencia C5055 de nivel 1 cuando la opción del compilador /std:c++latest está habilitada (/std:c++20 en Visual Studio 2019, versión 16.11 y posteriores):

enum E1 { a };
int main() {
  double i = a * 1.1;
}

Para evitar la advertencia, use static_cast para convertir el segundo operando:

enum E1 { a };
int main() {
   double i = static_cast<int>(a) * 1.1;
}

Comparaciones relacionales y de igualdad de matrices

Las comparaciones relacionales y de igualdad entre dos operandos de tipo de matriz están en desuso en C++20 (P1120R0). En otras palabras, una operación de comparación entre dos matrices (a pesar de las similitudes de rango y extensión) ahora es una advertencia. En Visual Studio 2019, versión 16.2 y posteriores, el código siguiente produce una advertencia C5056 de nivel 1 cuando la opción del compilador /std:c++latest está habilitada (/std:c++20 en Visual Studio 2019, versión 16.11 y posteriores):

int main() {
    int a[] = { 1, 2, 3 };
    int b[] = { 1, 2, 3 };
    if (a == b) { return 1; } // warning C5056: operator '==': deprecated for array types
}

Para evitar la advertencia, puede comparar las direcciones de los primeros elementos:

int main() {
    int a[] = { 1, 2, 3 };
    int b[] = { 1, 2, 3 };
    if (&a[0] == &b[0]) { return 1; }
}

Para determinar si el contenido de dos matrices es igual, use la función std::equal:

std::equal(std::begin(a), std::end(a), std::begin(b), std::end(b));

Efecto de la definición del "operador de nave espacial" en == y !=

Una definición del "operador de nave espacial" (<=>) por sí sola ya no reescribirá expresiones relacionadas con == o != a menos que el "operador de nave espacial" se marque como = default (P1185R2). El ejemplo siguiente se compila en Visual Studio 2019 RTW y en la versión 16.1, pero se produce C2678 en la versión 16.2 de Visual Studio 2019:

#include <compare>

struct S {
  int a;
  auto operator<=>(const S& rhs) const {
    return a <=> rhs.a;
  }
};
bool eq(const S& lhs, const S& rhs) {
  return lhs == rhs; // error C2676
}
bool neq(const S& lhs, const S& rhs) {
    return lhs != rhs; // error C2676
}

Para evitar el error, defina el operator== o declárelo como valor predeterminado:

#include <compare>

struct S {
  int a;
  auto operator<=>(const S& rhs) const {
    return a <=> rhs.a;
  }
  bool operator==(const S&) const = default;
};
bool eq(const S& lhs, const S& rhs) {
  return lhs == rhs;
}
bool neq(const S& lhs, const S& rhs) {
    return lhs != rhs;
}

Mejoras de la biblioteca estándar

  • <charconv>to_chars() con precisión fija o científica. (La precisión general está planeada actualmente para la versión 16.4).
  • P0020R6: atomic<float>, atomic<double>, atomic<long double>
  • P0463R1: endian
  • P0482R6: Compatibilidad de la biblioteca con char8_t
  • P0600R1: [[nodiscard]] para STL, parte 1
  • P0653R2: to_address()
  • P0754R2: <versión>
  • P0771R1: noexcept para el constructor de movimiento de std::function

Comparadores const para contenedores asociativos

El código para la búsqueda y la inserción en set, map, multisety multimap se ha combinado para reducirlo de tamaño. Las operaciones de inserción ahora llaman a la comparación menor que en el functor de comparación const, del mismo modo en que actuaban previamente las operaciones de búsqueda. El código siguiente se compila en la versión 16.1 de Visual Studio 2019 y en versiones anteriores, pero genera C3848 en la versión 16.2 de Visual Studio 2019:

#include <iostream>
#include <map>

using namespace std;

struct K
{
   int a;
   string b = "label";
};

struct Comparer  {
   bool operator() (K a, K b) {
      return a.a < b.a;
   }
};

map<K, double, Comparer> m;

K const s1{1};
K const s2{2};
K const s3{3};

int main() {

   m.emplace(s1, 1.08);
   m.emplace(s2, 3.14);
   m.emplace(s3, 5.21);

}

Para evitar el error, use el operador de comparación const:

struct Comparer  {
   bool operator() (K a, K b) const {
      return a.a < b.a;
   }
};

Mejoras de conformidad en Visual Studio 2019 versión 16.3

Se han quitado los operadores de extracción de secuencias para char*.

Se han quitado los operadores de extracción de secuencias para puntero a caracteres y se han reemplazado por operadores de extracción para matriz de caracteres (por P0487R1). WG21 considera que las sobrecargas eliminadas no son seguras. En el modo /std:c++20 o /std:c++latest , el ejemplo siguiente produce ahora C2679:

// stream_extraction.cpp
// compile by using: cl /std:c++latest stream_extraction.cpp

#include <iostream>
#include <iomanip>

int main() {
    char x[42];
    char* p = x;
    std::cin >> std::setw(42);
    std::cin >> p;  // C2679: binary '>>': no operator found which takes a right-hand operand of type 'char *' (or there is no acceptable conversion)
}

Para evitar el error, use el operador de extracción con una variable char[]:

#include <iostream>
#include <iomanip>

int main() {
    char x[42];
    std::cin >> std::setw(42);
    std::cin >> x;  // OK
}

Nuevas palabras clave requires y concept

Se han agregado nuevas palabras clave requires y concept al compilador de Microsoft C++. Si intenta usar cualquiera de ellas como identificador en modo /std:c++20 o /std:c++latest, el compilador genera C2059 para indicar un error de sintaxis.

Los constructores como nombres de tipo no están permitidos

El compilador ya no considera los nombres de constructor como injected-class-names en este caso: cuando aparecen en un nombre completo después de un alias de una especialización de platilla de clase. Anteriormente, los constructores se podían usar como un nombre de tipo para declarar otras entidades. El siguiente ejemplo genera el error C3646:

#include <chrono>

class Foo {
   std::chrono::milliseconds::duration TotalDuration{}; // C3646: 'TotalDuration': unknown override specifier
};

Para evitar el error, declare TotalDuration como se muestra aquí:

#include <chrono>

class Foo {
  std::chrono::milliseconds TotalDuration {};
};

Comprobación más estricta de las funciones extern "C"

Si se declaraba una función extern "C" en espacios de nombres diferentes, las versiones anteriores del compilador de Microsoft C++ no comprobaban si las declaraciones eran compatibles. En Visual Studio 2019, versión 16.3 y posteriores, el compilador busca compatibilidad. En el modo /permissive-, el código siguiente produce los erroresC2371 y C2733:

using BOOL = int;

namespace N
{
   extern "C" void f(int, int, int, bool);
}

void g()
{
   N::f(0, 1, 2, false);
}

extern "C" void f(int, int, int, BOOL){}
    // C2116: 'N::f': function parameter lists do not match between declarations
    // C2733: 'f': you cannot overload a function with 'extern "C"' linkage

Para evitar los errores del ejemplo anterior, use bool en lugar de BOOL de forma coherente en ambas declaraciones de f.

Mejoras de la biblioteca estándar

Se han quitado los encabezados no estándar <stdexcpt.h> y <typeinfo.h>. En su lugar, el código que los incluye debe incluir los encabezados estándar <exception> y <typeinfo>, respectivamente.

Mejoras de conformidad en Visual Studio 2019 versión 16.4

Mejor aplicación de la búsqueda de nombres en dos fases para identificadores cualificados en /permissive-

La búsqueda de nombres en dos fases requiere que los nombres no dependientes que se usan en los cuerpos de las plantillas sean visibles para la plantilla en el momento de la definición. Anteriormente, es posible que estos nombres se hayan encontrado cuando se creaban instancias de las plantillas. Este cambio facilita la escritura de código portable y compatible en MSVC con la marca /permissive-.

En la versión 16.4 de Visual Studio 2019, con la marca /permissive- establecida, en el ejemplo siguiente se genera un error porque N::f no es visible cuando se define la plantilla f<T>:

template <class T>
int f() {
    return N::f() + T{}; // error C2039: 'f': is not a member of 'N'
}

namespace N {
    int f() { return 42; }
}

Normalmente, este error se puede corregir al incluir los encabezados que faltan o las funciones o variables de declaración de reenvío, tal como se muestra en el ejemplo siguiente:

namespace N {
    int f();
}

template <class T>
int f() {
    return N::f() + T{};
}

namespace N {
    int f() { return 42; }
}

Conversión implícita de expresiones constantes de tipo entero en puntero nulo

Ahora, el compilador de MSVC implementa la incidencia 903 de CWF en modo de cumplimiento (/permissive-). Esta regla no permite la conversión implícita de expresiones constantes integrales (excepto el literal entero '0') a constantes de puntero nulo. En el ejemplo siguiente se produce C2440 en modo de cumplimiento:

int* f(bool* p) {
    p = false; // error C2440: '=': cannot convert from 'bool' to 'bool *'
    p = 0; // OK
    return false; // error C2440: 'return': cannot convert from 'bool' to 'int *'
}

Para resolver el error, use nullptr en lugar de false . El literal 0 sigue permitido:

int* f(bool* p) {
    p = nullptr; // OK
    p = 0; // OK
    return nullptr; // OK
}

Reglas estándar para tipos de literales enteros

En el modo de cumplimiento (habilitado por /permissive-), MSVC utiliza las reglas estándar para los tipos de literales enteros. Anteriormente los literales decimales demasiado grandes para caber en un signed int tenían el tipo unsigned int . Ahora, estos literales reciben el siguiente tipo de entero signed más grande, long long . Además, los literales con el sufijo "ll", que son demasiado grandes para caber en un tipo signed reciben el tipo unsigned long long .

Este cambio puede dar lugar a que se generen diagnósticos de advertencia diferentes y diferencias de comportamiento para las operaciones aritméticas en los literales.

En el ejemplo siguiente se muestra el nuevo comportamiento en Visual Studio 2019 versión 16.4. La variable i es ahora de tipo unsigned int, por lo que se muestra la advertencia. Los bits de orden superior de la variable j se establecen en 0.

void f(int r) {
    int i = 2964557531; // warning C4309: truncation of constant value
    long long j = 0x8000000000000000ll >> r; // literal is now unsigned, shift will fill high-order bits with 0
}

En el ejemplo siguiente se muestra cómo mantener el comportamiento anterior y evitar las advertencias y el cambio de comportamiento en tiempo de ejecución:

void f(int r) {
int i = 2964557531u; // OK
long long j = (long long)0x8000000000000000ll >> r; // shift will keep high-order bits
}

Parámetros de función que reemplazan los parámetros de plantilla

El compilador MSVC ahora genera un error cuando un parámetro de función reemplaza un parámetro de plantilla:

template<typename T>
void f(T* buffer, int size, int& size_read);

template<typename T, int Size>
void f(T(&buffer)[Size], int& Size) // error C7576: declaration of 'Size' shadows a template parameter
{
    return f(buffer, Size, Size);
}

Para corregir el error, cambie el nombre de uno de los parámetros:

template<typename T>
void f(T* buffer, int size, int& size_read);

template<typename T, int Size>
void f(T (&buffer)[Size], int& size_read)
{
    return f(buffer, Size, size_read);
}

Especializaciones proporcionadas por el usuario de rasgos de tipo

En cumplimiento con la subcláusula meta.rqmts del estándar, el compilador MSVC ahora genera un error cuando encuentra una especialización definida por el usuario de una de las plantillas type_traits especificadas en el espacio de nombres std. A menos que se especifique lo contrario, estas especializaciones producen un comportamiento indefinido. En el ejemplo siguiente se muestra un comportamiento indefinido porque infringe la regla y static_assert produce un error C2338.

#include <type_traits>
struct S;

template<>
struct std::is_fundamental<S> : std::true_type {};

static_assert(std::is_fundamental<S>::value, "fail");

Para evitar el error, defina una estructura que herede el elemento type_trait preferente y especialícela:

#include <type_traits>

struct S;

template<typename T>
struct my_is_fundamental : std::is_fundamental<T> {};

template<>
struct my_is_fundamental<S> : std::true_type { };

static_assert(my_is_fundamental<S>::value, "fail");

Cambios en los operadores de comparación proporcionados por el compilador

Ahora, el compilador de MSVC implementa los siguientes cambios en los operadores de comparación por P1630R1 cuando la opción /std:c++20 o /std:c++latest está habilitada:

El compilador ya no volverá a escribir expresiones con operator== si implican un tipo de valor devuelto que no sea bool . El código siguiente produce ahora el error C2088:

struct U {
    operator bool() const;
};

struct S {
    U operator==(const S&) const;
};

bool neq(const S& lhs, const S& rhs) {
    return lhs != rhs;  // C2088: '!=': illegal for struct
}

Para evitar el error, debe definir explícitamente el operador necesario:

struct U {
    operator bool() const;
};

struct S {
    U operator==(const S&) const;
    U operator!=(const S&) const;
};

bool neq(const S& lhs, const S& rhs) {
    return lhs != rhs;
}

El compilador ya no definirá un operador de comparación predeterminado si es miembro de una clase union-like. El siguiente ejemplo genera ahora el error C2120:

#include <compare>

union S {
    int a;
    char b;
    auto operator<=>(const S&) const = default;
};

bool lt(const S& lhs, const S& rhs) {
    return lhs < rhs;
}

Para evitar el error, defina un cuerpo para el operador:

#include <compare>

union S {
    int a;
    char b;
    auto operator<=>(const S&) const { ... }
};

bool lt(const S& lhs, const S& rhs) {
    return lhs < rhs;
}

El compilador ya no definirá un operador de comparación predeterminado si la clase contiene un miembro de referencia. El código siguiente produce ahora el error C2120:

#include <compare>

struct U {
    int& a;
    auto operator<=>(const U&) const = default;
};

bool lt(const U& lhs, const U& rhs) {
    return lhs < rhs;
}

Para evitar el error, defina un cuerpo para el operador:

#include <compare>

struct U {
    int& a;
    auto operator<=>(const U&) const { ... };
};

bool lt(const U& lhs, const U& rhs) {
    return lhs < rhs;
}

Mejoras de conformidad en Visual Studio 2019 versión 16.5

La declaración de especialización explícita sin un inicializador no es una definición.

En /permissive-, MSVC ahora aplica una regla estándar que establece que las declaraciones de especialización explícita sin inicializadores no son definiciones. Anteriormente, la declaración se consideraría una definición con un inicializador predeterminado. El efecto es observable en el momento de la vinculación, ya que es posible que un programa que dependa de este comportamiento tenga ahora símbolos sin resolver. Este ejemplo genera ahora un error:

template <typename> struct S {
    static int a;
};

// In permissive-, this declaration isn't a definition, and the program won't link.
template <> int S<char>::a;

int main() {
    return S<char>::a;
}
error LNK2019: unresolved external symbol "public: static int S<char>::a" (?a@?$S@D@@2HA) referenced in function _main at link time.

Para resolver el problema, agregue un inicializador:

template <typename> struct S {
    static int a;
};

// Add an initializer for the declaration to be a definition.
template <> int S<char>::a{};

int main() {
    return S<char>::a;
}

La salida del preprocesador conserva las nuevas líneas

El preprocesador experimental conserva ahora las nuevas líneas y los espacios en blanco al usar /P o /E con /experimental:preprocessor.

Dado este origen de ejemplo,

#define m()
line m(
) line

El formato de salida anterior de /E era:

line line
#line 2

La nueva salida de /E ahora es:

line
 line

Las palabras clave import y module dependen del contexto.

De acuerdo con P1857R1, las directivas de preprocesador de import y de module tienen restricciones nuevas en su sintaxis. Este ejemplo ya no se compila:

import // Invalid
m;     // error C2146: syntax error: missing ';' before identifier 'm'

Para resolver el problema, mantenga la importación en la misma línea:

import m; // OK

Eliminación de std::weak_equality y std::strong_equality

La combinación de P1959R0 requiere que el compilador quite el comportamiento y las referencias a los tipos std::weak_equality y std::strong_equality.

El código de este ejemplo ya no se compila:

#include <compare>

struct S {
    std::strong_equality operator<=>(const S&) const = default;
};

void f() {
    nullptr<=>nullptr;
    &f <=> &f;
    &S::operator<=> <=> &S::operator<=>;
}

El ejemplo ahora conduce a estos errores:

error C2039: 'strong_equality': is not a member of 'std'
error C2143: syntax error: missing ';' before '<=>'
error C4430: missing type specifier - int assumed. Note: C++ does not support default-int
error C4430: missing type specifier - int assumed. Note: C++ does not support default-int
error C7546: binary operator '<=>': unsupported operand types 'nullptr' and 'nullptr'
error C7546: binary operator '<=>': unsupported operand types 'void (__cdecl *)(void)' and 'void (__cdecl *)(void)'
error C7546: binary operator '<=>': unsupported operand types 'int (__thiscall S::* )(const S &) const' and 'int (__thiscall S::* )(const S &) const'

Para resolver el problema, realice una actualización para dar preferencia a los operadores relacionales integrados y reemplazar los tipos quitados:

#include <compare>

struct S {
    std::strong_ordering operator<=>(const S&) const = default; // prefer 'std::strong_ordering'
};

void f() {
    nullptr != nullptr; // use pre-existing builtin operator != or ==.
    &f != &f;
    &S::operator<=> != &S::operator<=>;
}

Cambios en TLS Guard

Anteriormente, las variables locales de subprocesos en los archivos DLL no se inicializaban de manera correcta. Además de en el subproceso que cargó el archivo DLL, no se inicializaban antes de usarse por primera vez en los subprocesos que existían antes de que se cargara el archivo DLL. Este defecto se ha corregido. Las variables locales de subprocesos de este tipo de archivo DLL se inicializan inmediatamente antes de su primer uso en esos subprocesos.

Este nuevo comportamiento de pruebas de inicialización en usos de variables locales de subproceso se puede deshabilitar mediante la opción del compilador /Zc:tlsGuards- . O bien, agregando el atributo [[msvc:no_tls_guard]] a determinadas variables locales de subproceso.

Mejor diagnóstico de la llamada a funciones eliminadas

Nuestro compilador era más permisivo en las llamadas a funciones eliminadas previamente. Por ejemplo, si las llamadas se producían en el contexto de un cuerpo de plantilla, no se diagnosticaría la llamada. Además, si hubiera varias instancias de llamadas a funciones eliminadas, solo se emitiría un diagnóstico. Ahora se emite un diagnóstico para cada una de ellas.

Una consecuencia del nuevo comportamiento puede producir un pequeño cambio importante: El código que llamaba a una función eliminada no se diagnosticaría si nunca era necesario para la generación de código. Ahora se diagnostica por adelantado.

En este ejemplo se muestra código que ahora genera un error:

struct S {
  S() = delete;
  S(int) { }
};

struct U {
  U() = delete;
  U(int i): s{ i } { }

  S s{};
};

U u{ 0 };
error C2280: 'S::S(void)': attempting to reference a deleted function
note: see declaration of 'S::S'
note: 'S::S(void)': function was explicitly deleted

Para resolver el problema, quite las llamadas a funciones eliminadas:

struct S {
  S() = delete;
  S(int) { }
};

struct U {
  U() = delete;
  U(int i): s{ i } { }

  S s;  // Do not call the deleted ctor of 'S'.
};

U u{ 0 };

Mejoras de conformidad en Visual Studio 2019 versión 16.6

Las secuencias de la biblioteca estándar rechazan las inserciones de tipos de caracteres con codificación incorrecta

Tradicionalmente, al insertar wchar_t en std::ostream y al insertar char16_t o char32_t en std::ostream o std::wostream, se genera su valor entero. Al insertar punteros a esos tipos de caracteres, se genera el valor del puntero. Los programadores no encuentran ningún caso intuitivo. A menudo esperan que la biblioteca estándar transcodifique el carácter o la cadena de caracteres terminada en null en su lugar, y que genere el resultado.

La propuesta de C++20 P1423R3 agrega sobrecargas de operador de inserción de secuencias eliminadas para estas combinaciones de secuencia y carácter o de tipos de puntero de carácter. En /std:c++20 o /std:c++latest , las sobrecargas hacen que estas inserciones estén mal formadas, en lugar de comportarse de manera probablemente no intencionada. El compilador genera el error C2280 cuando se encuentra una. Puede definir la macro "sombreado de escape" _HAS_STREAM_INSERTION_OPERATORS_DELETED_IN_CXX20 a 1 para restaurar el comportamiento anterior. (La propuesta también elimina los operadores de inserción de secuencias para char8_t. Nuestra biblioteca estándar implementaba sobrecargas similares cuando se agregaba compatibilidad con char8_t, así que el comportamiento "erróneo" nunca ha estado disponible para char8_t).

En este ejemplo se muestra el comportamiento de este cambio:

#include <iostream>
int main() {
    const wchar_t cw = L'x', *pw = L"meow";
    const char16_t c16 = u'x', *p16 = u"meow";
    const char32_t c32 = U'x', *p32 = U"meow";
    std::cout << cw << ' ' << pw << '\n';
    std::cout << c16 << ' ' << p16 << '\n';
    std::cout << c32 << ' ' << p32 << '\n';
    std::wcout << c16 << ' ' << p16 << '\n';
    std::wcout << c32 << ' ' << p32 << '\n';
}

El código genera ahora estos mensajes de diagnóstico:

error C2280: 'std::basic_ostream<char,std::char_traits<char>> &std::<<<std::char_traits<char>>(std::basic_ostream<char,std::char_traits<char>> &,wchar_t)': attempting to reference a deleted function
error C2280: 'std::basic_ostream<char,std::char_traits<char>> &std::<<<std::char_traits<char>>(std::basic_ostream<char,std::char_traits<char>> &,char16_t)': attempting to reference a deleted function
error C2280: 'std::basic_ostream<char,std::char_traits<char>> &std::<<<std::char_traits<char>>(std::basic_ostream<char,std::char_traits<char>> &,char32_t)': attempting to reference a deleted function
error C2280: 'std::basic_ostream<wchar_t,std::char_traits<wchar_t>> &std::<<<std::char_traits<wchar_t>>(std::basic_ostream<wchar_t,std::char_traits<wchar_t>> &,char16_t)': attempting to reference a deleted function
error C2280: 'std::basic_ostream<wchar_t,std::char_traits<wchar_t>> &std::<<<std::char_traits<wchar_t>>(std::basic_ostream<wchar_t,std::char_traits<wchar_t>> &,char32_t)': attempting to reference a deleted function

Puede lograr el efecto del comportamiento anterior en todos los modos de lenguaje al convertir los tipos de caracteres en unsigned int o los tipos de puntero a carácter para const void*:

#include <iostream>
int main() {
    const wchar_t cw = L'x', *pw = L"meow";
    const char16_t c16 = u'x', *p16 = u"meow";
    const char32_t c32 = U'x', *p32 = U"meow";
    std::cout << (unsigned)cw << ' ' << (const void*)pw << '\n'; // Outputs "120 0052B1C0"
    std::cout << (unsigned)c16 << ' ' << (const void*)p16 << '\n'; // Outputs "120 0052B1CC"
    std::cout << (unsigned)c32 << ' ' << (const void*)p32 << '\n'; // Outputs "120 0052B1D8"
    std::wcout << (unsigned)c16 << ' ' << (const void*)p16 << '\n'; // Outputs "120 0052B1CC"
    std::wcout << (unsigned)c32 << ' ' << (const void*)p32 << '\n'; // Outputs "120 0052B1D8"
}

Tipo de valor devuelto cambiado de std::pow() para std::complex

Anteriormente, la implementación de MSVC de las reglas de promoción para el tipo de valor devuelto de la plantilla de función std::pow() era incorrecta. Por ejemplo, anteriormente pow(complex<float>, int) devolvía complex<float>. Ahora devuelve complex<double> correctamente. La corrección se ha implementado de forma incondicional en todos los modos estándar de Visual Studio 2019, versión 16.6.

Este cambio puede provocar errores del compilador. Por ejemplo, antes podía multiplicar pow(complex<float>, int) por un float . Dado que complex<T> operator* espera argumentos del mismo tipo, el siguiente ejemplo emite ahora el error del compilador C2676:

// pow_error.cpp
// compile by using: cl /EHsc /nologo /W4 pow_error.cpp
#include <complex>

int main() {
    std::complex<float> cf(2.0f, 0.0f);
    (void) (std::pow(cf, -1) * 3.0f);
}
pow_error.cpp(7): error C2676: binary '*': 'std::complex<double>' does not define this operator or a conversion to a type acceptable to the predefined operator

Hay muchas posibles correcciones:

  • Cambie el tipo del multiplicando float a double . Este argumento se puede convertir directamente en un complex<double> para que coincida con el tipo devuelto por pow.

  • Restrinja el resultado de pow a complex<float> diciendo complex<float>{pow(ARG, ARG)}. Después, puede seguir multiplicando por un valor float .

  • Pase float en lugar de int a pow. Esta operación puede ser más lenta.

  • En algunos casos, puede evitar pow completamente. Por ejemplo, pow(cf, -1) se puede reemplazar por la división.

Advertencias switch para C

En Visual Studio 2019, versión 16.6 y posteriores, el compilador implementa algunas advertencias preexistentes de C++ para el código compilado como C. Las siguientes advertencias se habilitan ahora en diferentes niveles: C4060, C4061, C4062, C4063, C4064, C4065, C4808 y C4809. Las advertencias C4065 y C4060 están deshabilitadas de forma predeterminada en C.

Las advertencias se desencadenan cuando faltan instrucciones case , elementos enum no definidos e instrucciones de modificador bool erróneos (es decir, aquellos que contienen demasiados casos). Por ejemplo:

#include <stdbool.h>

int main() {
    bool b = true;
    switch (b) {
        case true: break;
        case false: break;
        default: break; // C4809: switch statement has redundant 'default' label;
                        // all possible 'case' labels are given
    }
}

Para corregir este código, quite el caso default redundante:

#include <stdbool.h>

int main() {
    bool b = true;
    switch (b) {
        case true: break;
        case false: break;
    }
}

Clases sin nombre en declaraciones typedef

En Visual Studio 2019, versión 16.6 y posteriores, el comportamiento de las declaraciones typedef se ha restringido para ajustarse a P1766R1. Con esta actualización, las clases sin nombre dentro de una declaración typedef no pueden tener ningún miembro que no sean:

  • Miembros de datos no estáticos sin inicializadores de miembro predeterminados
  • Clases de miembro
  • Enumeraciones de miembros

Las mismas restricciones se aplican de forma recursiva a cada clase anidada. La restricción está pensada para garantizar la simplicidad de las estructuras que tienen nombres typedef para la vinculación. Deben ser lo suficientemente simples como para que no sea necesario realizar cálculos de vinculación antes de que el compilador obtenga el nombre de typedef para la vinculación.

Este cambio afecta a todos los modos estándar del compilador. En los modos predeterminados (/std:c++14) y /std:c++17, el compilador emite la advertencia C5208 para el código que no es conforme. Si se especifica /permissive- , el compilador emite la advertencia C5208 como un error en /std:c++14 y emite el error C7626 en /std:c++17 . El compilador emite el error C7626 para el código que no cumple las especificaciones cuando se determina /std:c++20 o /std:c++latest .

En el ejemplo siguiente se muestran las construcciones que ya no se permiten en las estructuras sin nombre. Según el modo estándar especificado, se emiten errores o advertencias de C5208 o C7626:

struct B { };
typedef struct : B { // inheriting from 'B'; ill-formed
    void f(); // ill-formed
    static int i; // ill-formed
    struct U {
        void f(); // nested class has non-data member; ill-formed
    };
    int j = 10; // default member initializer; ill-formed
} S;

El código anterior se puede corregir asignando un nombre a la clase sin nombre:

struct B { };
typedef struct S_ : B {
    void f();
    static int i;
    struct U {
        void f();
    };
    int j = 10;
} S;

Importación de argumentos predeterminados en C++ o CLI

Cada vez más API tienen argumentos predeterminados en .NET Core. Por este motivo, ahora se admite la importación de argumentos predeterminados en C++/CLI. Este cambio puede afectar al código existente en el que se declaran varias sobrecargas, como en este ejemplo:

public class R {
    public void Func(string s) {}   // overload 1
    public void Func(string s, string s2 = "") {} // overload 2;
}

Cuando esta clase se importa en C++ o CLI, una llamada a una de las sobrecargas produce un error:

    (gcnew R)->Func("abc"); // error C2668: 'R::Func' ambiguous call to overloaded function

El compilador emite el error C2668 porque ambas sobrecargas coinciden con esta lista de argumentos. En la segunda sobrecarga, el segundo argumento se rellena con el argumento predeterminado. Para solucionar este problema, puede eliminar la sobrecarga redundante (1). O bien, use la lista de argumentos completa y proporcione explícitamente los argumentos predeterminados.

Mejoras de conformidad en Visual Studio 2019 versión 16.7

La definición de se puede copiar de manera trivial

C++20 cambió la definición de se puede copiar de manera trivial. Cuando una clase tiene un miembro de datos no estáticos con tipo calificado volatile , ya no implica que todo constructor de movimiento o copia generada por el compilador, u operador de asignación de movimiento o copia, es no trivial. El comité de estándares de C++ aplicó este cambio de manera retroactiva como informe de defectos. En MSVC, el comportamiento del compilador no cambia en los distintos modos de lenguaje, como /std:c++14 o /std:c++latest .

Este es un ejemplo del comportamiento nuevo:

#include <type_traits>

struct S
{
    volatile int m;
};

static_assert(std::is_trivially_copyable_v<S>, "Meow!");

Este código no se compila en las versiones de MSVC antes de la versión 16.7 de Visual Studio 2019. Hay una advertencia del compilador desactivada de forma predeterminada que puede usar para detectar este cambio. Si compila el código anterior mediante cl /W4 /w45220 , verá la advertencia siguiente:

warning C5220: `'S::m': a non-static data member with a volatile qualified type no longer implies that compiler generated copy/move constructors and copy/move assignment operators are non trivial`

Las conversiones de puntero a miembro y de literal de cadena a bool son conversiones de restricción

Recientemente, el comité de estándares de C++ adoptó el informe de defectos P1957R2, que considera de T* a bool una conversión de restricción. MSVC corrigió un error en su implementación, que anteriormente diagnosticaba de T* a bool como conversión de restricción, pero no diagnosticaba la conversión de un literal de cadena a bool o de un puntero a miembro a bool .

El programa siguiente tiene un formato incorrecto en la versión 16.7 de Visual Studio 2019:

struct X { bool b; };
void f(X);

int main() {
    f(X { "whoops?" }); // error: conversion from 'const char [8]' to 'bool' requires a narrowing conversion

    int (X::* p) = nullptr;
    f(X { p }); // error: conversion from 'int X::*' to 'bool' requires a narrowing conversion
}

Para corregir este código, agregue comparaciones explícitas a nullptr o evite contextos donde las conversiones de restricción tienen un formato incorrecto:

struct X { bool b; };
void f(X);

int main() {
    f(X { "whoops?" != nullptr }); // Absurd, but OK

    int (X::* p) = nullptr;
    f(X { p != nullptr }); // OK
}

nullptr_t solo se puede convertir en bool como una inicialización directa

En C++11, nullptr solo se puede convertir en bool como una conversión directa; por ejemplo, al inicializar un valor bool con una lista de inicializadores entre llaves. MSVC nunca aplicó esta restricción. MSVC ahora implementa la regla en /permissive-. Ahora, las conversiones implícitas se diagnostican con formato incorrecto. De todos modos se permite una conversión contextual a bool , porque la inicialización directa bool b(nullptr) es válida.

En la mayoría de los casos, es posible corregir el error si se reemplaza nullptr por false , tal como se muestra en este ejemplo:

struct S { bool b; };
void g(bool);
bool h() { return nullptr; } // error, should be 'return false;'

int main() {
    bool b1 = nullptr; // error: cannot convert from 'nullptr' to 'bool'
    S s { nullptr }; // error: cannot convert from 'nullptr' to 'bool'
    g(nullptr); // error: cannot convert argument 1 from 'nullptr' to 'bool'

    bool b2 { nullptr }; // OK: Direct-initialization
    if (!nullptr) {} // OK: Contextual conversion to bool
}

Conformidad del comportamiento de inicialización para las inicializaciones de matriz con inicializadores faltantes

Anteriormente, MSVC tenía un comportamiento no compatible para las inicializaciones de matriz que tenían inicializadores faltantes. MSVC siempre llamó al constructor predeterminado para cada elemento de matriz que no tenía un inicializador. El comportamiento estándar es inicializar cada elemento con una lista de inicializadores entre llaves vacía ( {} ). El contexto de inicialización de una lista de inicializadores entre llaves vacía es la inicialización de copia, que no permite llamadas a constructores explícitos. También puede haber diferencias de tiempo de ejecución, porque el uso de {} para la inicialización puede llamar a un constructor que lleva un elemento std::initializer_list, en lugar del constructor predeterminado. El comportamiento conforme se habilita en /permissive-.

Este es un ejemplo del comportamiento modificado:

struct B {
    explicit B() {}
};

void f() {
    B b1[1]{}; // Error in /permissive-, because aggregate init calls explicit ctor
    B b2[1]; // OK: calls default ctor for each array element
}

La inicialización de los miembros de clase con nombres sobrecargados sigue una secuencia correcta

Se identificó un error en la representación interna de los miembros de datos de clase cuando un nombre de tipo también está sobrecargado como un nombre de miembro de datos. Este error provocaba incoherencias en el orden de la inicialización de miembros y la inicialización de agregado. El código de inicialización generado ahora es correcto. Sin embargo, este cambio puede generar errores o advertencias en un origen que se basó inadvertidamente en miembros ordenados de manera incorrecta, como en el ejemplo siguiente:

// Compiling with /w15038 now gives:
// warning C5038: data member 'Outer::Inner' will be initialized after data member 'Outer::v'
struct Outer {
    Outer(int i, int j) : Inner{ i }, v{ j } {}

    struct Inner { int x; };
    int v;
    Inner Inner; // 'Inner' is both a type name and data member name in the same scope
};

En versiones anteriores, el constructor inicializaba de manera incorrecta el miembro de datos Inner antes del miembro de datos v. (El estándar de C++ requiere un orden de inicialización que sea igual al orden de declaración de los miembros). Ahora que el código generado sigue el estándar, la lista de inicializadores de miembros está desordenada. El compilador genera una advertencia para este ejemplo. Para corregirlo, reordene la lista de inicializadores de miembros para reflejar el orden de la declaración.

Resolución de sobrecarga que implica sobrecargas integrales y argumentos long

El estándar de C++ requiere clasificar una conversión de long a int como una conversión estándar. Los compiladores de MSVC anteriores la clasificaban incorrectamente como una promoción de entero, que tiene una clasificación superior para la resolución de sobrecargas. Esta clasificación puede hacer que la resolución de sobrecargas se resuelva correctamente cuando se debe considerar ambigua.

El compilador ahora considera la clasificación correctamente en el modo /permissive-. El código no válido se diagnostica de manera correcta, como en este ejemplo:

void f(long long);
void f(int);

int main() {
    long x {};
    f(x); // error: 'f': ambiguous call to overloaded function
    f(static_cast<int>(x)); // OK
}

Puede corregir este problema de varias maneras:

  • En el sitio de llamada, cambie el tipo del argumento pasado a int . Puede cambiar el tipo de variable o convertirlo.

  • Si hay muchos sitios de llamada, puede agregar otra sobrecarga que toma un argumento long . En esta función, convierta y desvíe el argumento a la sobrecarga int .

Uso de una variable no definida con vinculación interna

Las versiones de MSVC anteriores a la versión 16.7 de Visual Studio 2019 aceptaban el uso de una variable declarada extern que tenía una vinculación interna y que no estaba definida. Estas variables no se pueden definir en ninguna otra unidad de traducción y no pueden formar un programa válido. El compilador ahora diagnostica este caso en tiempo de compilación. El error es similar al error de las funciones estáticas no definidas.

namespace {
    extern int x; // Not a definition, but has internal linkage because of the anonymous namespace
}

int main()
{
    return x; // Use of 'x' that no other translation unit can possibly define.
}

Este programa se compiló y vinculó previamente de manera incorrecta, pero ahora emitirá el error C7631.

error C7631: 'anonymous-namespace::x': variable with internal linkage declared but not defined

Estas variables se deben definir en la misma unidad de traducción en la que se usan. Por ejemplo, puede proporcionar un inicializador explícito o una definición independiente.

Integridad de tipo y conversiones de puntero de derivado a base

En los estándares de C++ antes de C++20, una conversión de una clase derivada a una clase raíz no requería que la clase derivada fuese un tipo de clase completa. El comité de estándares de C++ ha aprobado un cambio de informe de defectos retroactivo que se aplica a todas las versiones del lenguaje C++. Este cambio alinea el proceso de conversión con rasgos de tipos, como std::is_base_of, que requieren que la clase derivada sea un tipo de clase completa.

Este es un ejemplo:

template<typename A, typename B>
struct check_derived_from
{
    static A a;
    static constexpr B* p = &a;
};

struct W { };
struct X { };
struct Y { };

// With this change this code will fail as Z1 is not a complete class type
struct Z1 : X, check_derived_from<Z1, X>
{
};

// This code failed before and it will still fail after this change
struct Z2 : check_derived_from<Z2, Y>, Y
{
};

// With this change this code will fail as Z3 is not a complete class type
struct Z3 : W
{
    check_derived_from<Z3, W> cdf;
};

Este cambio de comportamiento se aplica a todos los modos de lenguaje C++ de MSVC, no solo /std:c++20 o /std:c++latest .

Las conversiones de restricción se diagnostican de manera más coherente

MSVC emite una advertencia para las conversiones de restricción en un inicializador de lista entre llaves. Anteriormente, el compilador no podía diagnosticar conversiones de restricción de tipos subyacentes enum mayores a tipos enteros más restringidos. (El compilador los consideraba incorrectamente una promoción de entero en lugar de una conversión). Si la conversión de restricción es intencional, para evitar la advertencia puede usar static_cast en el argumento del inicializador. O bien, elija un tipo entero de destino mayor.

A continuación, se muestra un ejemplo de uso de un elemento static_cast explícito para solucionar la advertencia:

enum E : long long { e1 };
struct S { int i; };

void f(E e) {
    S s = { e }; // warning: conversion from 'E' to 'int' requires a narrowing conversion
    S s1 = { static_cast<int>(e) }; // Suppress warning with explicit conversion
}

Mejoras de cumplimiento en Visual Studio 2019 versión 16.8

Extensión "class rvalue used as lvalue"

MSVC tiene una extensión que permite el uso de una clase rvalue como valor lvalue. La extensión no amplía la duración de la clase rvalue y puede dar lugar a un comportamiento indefinido en tiempo de ejecución. Ahora se aplica la regla estándar y no se permite esta extensión en /permissive- . Si no puede usar aún /permissive- , puede utilizar /we4238 para no permitir explícitamente la extensión. Este es un ejemplo:

// Compiling with /permissive- now gives:
// error C2102: '&' requires l-value
struct S {};

S f();

void g()
{
    auto p1 = &(f()); // The temporary returned by 'f' is destructed after this statement. So 'p1' points to an invalid object.

    const auto &r = f(); // This extends the lifetime of the temporary returned by 'f'
    auto p2 = &r; // 'p2' points to a valid object
}

Extensión "explicit specialization in non-namespace scope"

MSVC tenía una extensión que permitía la especialización explícita en un ámbito distinto del espacio de nombres. Ahora forma parte del estándar, después de la resolución de CWG 727. Sin embargo, hay diferencias de comportamiento. Se ha ajustado el comportamiento de nuestro compilador en consonancia con el estándar.

// Compiling with 'cl a.cpp b.cpp /permissive-' now gives:
//   error LNK2005: "public: void __thiscall S::f<int>(int)" (??$f@H@S@@QAEXH@Z) already defined in a.obj
// To fix the linker error,
// 1. Mark the explicit specialization with 'inline' explicitly. Or,
// 2. Move its definition to a source file.

// common.h
struct S {
    template<typename T> void f(T);
    template<> void f(int);
};

// This explicit specialization is implicitly inline in the default mode.
template<> void S::f(int) {}

// a.cpp
#include "common.h"

int main() {}

// b.cpp
#include "common.h"

Comprobación de los tipos de clase abstracta

El estándar C++20 cambió el proceso que utilizan los compiladores para detectar el uso de un tipo de clase abstracta como parámetro de una función. En concreto, ya no es un error SFINAE. Anteriormente, si el compilador detectaba que una especialización de una plantilla de función tendría una instancia de un tipo de clase abstracta como parámetro de función, esa especialización se consideraba incorrecta. No se agregaba al conjunto de funciones viables candidatas. En C++20, la comprobación de un parámetro de tipo de clase abstracta no se produce hasta que se llama a la función. El efecto es que el código que se usa para compilar no producirá un error. Este es un ejemplo:

class Node {
public:
    int index() const;
};

class String : public Node {
public:
    virtual int size() const = 0;
};

class Identifier : public Node {
public:
    const String& string() const;
};

template<typename T>
int compare(T x, T y)
{
    return x < y ? -1 : (x > y ? 1 : 0);
}

int compare(const Node& x, const Node& y)
{
    return compare(x.index(), y.index());
}

int f(const Identifier& x, const String& y)
{
    return compare(x.string(), y);
}

Anteriormente, la llamada a compare habría intentado especializar la plantilla de función compare mediante un argumento de plantilla String para T. No se generaba una especialización válida porque String es una clase abstracta. El único candidato viable habría sido compare(const Node&, const Node&). Sin embargo, en C++20 la comprobación del tipo de clase abstracta no se produce hasta que se llama a la función. Por lo tanto, la especialización compare(String, String) se agrega al conjunto de candidatos viables, y se elige como el mejor candidato porque la conversión de const String& a String es una secuencia de conversión mejor que la conversión de const String& a const Node&.

En C++20, una posible solución para este ejemplo es usar conceptos; es decir, cambiar la definición de compare por:

template<typename T>
int compare(T x, T y) requires !std::is_abstract_v<T>
{
    return x < y ? -1 : (x > y ? 1 : 0);
}

O bien, si no hay conceptos de C++, puede revertir a SFINAE:

template<typename T, std::enable_if_t<!std::is_abstract_v<T>, int> = 0>
int compare(T x, T y)
{
    return x < y ? -1 : (x > y ? 1 : 0);
}

Compatibilidad con P0960R3: se permite la inicialización de agregados a partir de una lista de valores entre paréntesis

C++20 P0960R3 agrega compatibilidad para inicializar un agregado mediante una lista de inicializadores entre paréntesis. Por ejemplo, el código siguiente es válido en C++20:

struct S {
    int i;
    int j;
};

S s(1, 2);

La mayor parte de esta característica es aditiva, es decir, el código compila ahora lo que no compiló antes. Sin embargo, cambia el comportamiento de std::is_constructible. En el modo C++17, se produce un error de static_assert , pero en modo C++ 20 se ejecuta correctamente:

static_assert(std::is_constructible_v<S, int, int>, "Assertion failed!");

Si usa este rasgo de tipo para controlar la resolución de sobrecarga, puede provocar un cambio de comportamiento entre C++17 y C++20.

Resolución de la sobrecarga relacionada con las plantillas de función

Anteriormente, el compilador permitía compilar algún código en /permissive- que no debería compilar. El efecto era que el compilador llamaba a la función equivocada, lo que daba lugar a un cambio en el comportamiento en tiempo de ejecución:

int f(int);

namespace N
{
    using ::f;
    template<typename T>
    T f(T);
}

template<typename T>
void g(T&& t)
{
}

void h()
{
    using namespace N;
    g(f);
}

La llamada a g utiliza un conjunto de sobrecargas que contiene dos funciones, ::f y N::f. Puesto que N::f es una plantilla de función, el compilador debe tratar el argumento de la función como contexto no deducido. Esto significa que, en este caso, la llamada a g no se realizará, ya que el compilador no puede deducir un tipo para el parámetro de plantilla T. Por desgracia, el compilador no descartó el hecho de que ya había decidido que ::f era una buena opción para la llamada de función. En lugar de emitir un error, el compilador generaba código para llamar a g usando ::f como argumento.

Dado que, en muchos casos, el uso de ::f como argumento de la función es lo que el usuario espera, solo se emite un error si el código se compila con /permissive- .

Migración de /await a corrutinas de C++20

Las corrutinas de C++20 estándar ahora están activadas de forma predeterminada en /std:c++20 y /std:c++latest . Se diferencian de las corrutinas TS y de la compatibilidad con la opción /await . La migración de /await a las corrutinas estándar puede requerir algunos cambios en el código fuente.

Palabras clave no estándar

Las palabras clave antiguas await y yield no se admiten en modo C++20. En su lugar, el código debe usar co_await y co_yield . El modo estándar tampoco permite el uso de return en una corrutina. Cada return en una corrutina debe utilizar co_return .

// /await
task f_legacy() {
    ...
    await g();
    return n;
}
// /std:c++latest
task f() {
    ...
    co_await g();
    co_return n;
}

Tipos de initial_suspend/final_suspend

En /await , las funciones iniciales y de suspensión de la promesa se pueden declarar como que devuelven bool . Este comportamiento no es estándar. En C++20, estas funciones deben devolver un tipo de clase que admita await, con frecuencia uno de los tipos que admiten await triviales, std::suspend_always, si la función devolvió anteriormente true o std::suspend_never si devolvió false .

// /await
struct promise_type_legacy {
    bool initial_suspend() noexcept { return false; }
    bool final_suspend() noexcept { return true; }
    ...
};

// /std:c++latest
struct promise_type {
    auto initial_suspend() noexcept { return std::suspend_never{}; }
    auto final_suspend() noexcept { return std::suspend_always{}; }
    ...
};

Tipo de yield_value

En C++20, la función yield_value de la promesa debe devolver un valor que admita await. En el modo /await , se permitía que la función yield_value devolviera void , y siempre se suspendía. Estas funciones se pueden reemplazar por una función que devuelva std::suspend_always.

// /await
struct promise_type_legacy {
    ...
    void yield_value(int x) { next = x; };
};

// /std:c++latest
struct promise_type {
    ...
    auto yield_value(int x) { next = x; return std::suspend_always{}; }
};

Función de control de excepciones

/await admite un tipo de promesa que no tiene una función de control de excepciones o que tiene una función de control de excepciones llamada set_exception que toma un tipo std::exception_ptr. En C++20, el tipo de promesa debe tener una función llamada unhandled_exception que no toma ningún argumento. El objeto de excepción se puede obtener de std::current_exception si es necesario.

// /await
struct promise_type_legacy {
    void set_exception(std::exception_ptr e) { saved_exception = e; }
    ...
};
// /std:c++latest
struct promise_type {
    void unhandled_exception() { saved_exception = std::current_exception(); }
    ...
};

No se admiten tipos devueltos deducidos de las corrutinas

C++20 no admite corrutinas con un tipo de valor devuelto que incluya un tipo de marcador de posición, como auto . Los tipos de valor devuelto de las corrutinas se deben declarar explícitamente. En /await, estos tipos deducidos siempre implican un tipo experimental y necesitan la inclusión de un encabezado que defina el tipo necesario: std::experimental::task<T>, std::experimental::generator<T> o std::experimental::async_stream<T>.

// /await
auto my_generator() {
    ...
    co_yield next;
};

// /std:c++latest
#include <experimental/generator>
std::experimental::generator<int> my_generator() {
    ...
    co_yield next;
};

Tipo de valor devuelto de return_value

El tipo de valor devuelto de la función return_value de la promesa debe ser void . En el modo /await , el tipo de valor devuelto puede ser cualquier cosa y se omite. Este diagnóstico puede ayudar a detectar errores sutiles, como cuando el autor asume incorrectamente que el valor devuelto de return_value se devuelve al autor de una llamada.

// /await
struct promise_type_legacy {
    ...
    int return_value(int x) { return x; } // incorrect, the return value of this function is unused and the value is lost.
};

// /std:c++latest
struct promise_type {
    ...
    void return_value(int x) { value = x; }; // save return value
};

Comportamiento de la conversión de objetos devueltos

Si el tipo de valor devuelto declarado de una corrutina no coincide con el tipo de valor devuelto de la función get_return_object de la promesa, el objeto devuelto de get_return_object se convierte en el tipo de valor devuelto de la corrutina. En /await , esta conversión se realiza con anterioridad, antes de que el cuerpo de la corrutina tenga la oportunidad de ejecutarse. En /std:c++20 o /std:c++latest , esta conversión se realiza cuando el valor se devuelve al autor de llamada. Permite corrutinas que no se suspenden en el punto de suspensión inicial para hacer uso del objeto devuelto por get_return_object dentro del cuerpo de la corrutina.

Parámetros de la promesa de corrutina

En C++20, el compilador intenta pasar los parámetros de corrutina (si existen) a un constructor del tipo de promesa. Si no lo logra, vuelve a intentarlo con un constructor predeterminado. En modo /await , solo se usaba el constructor predeterminado. Este cambio puede conducir a una diferencia de comportamiento si la promesa tiene varios constructores. O si hay una conversión de un parámetro de corrutina al tipo de promesa.

struct coro {
    struct promise_type {
        promise_type() { ... }
        promise_type(int x) { ... }
        ...
    };
};

coro f1(int x);

// Under /await the promise gets constructed using the default constructor.
// Under /std:c++latest the promise gets constructed using the 1-argument constructor.
f1(0);

struct Object {
template <typename T> operator T() { ... } // Converts to anything!
};

coro f2(Object o);

// Under /await the promise gets constructed using the default constructor
// Under /std:c++latest the promise gets copy- or move-constructed from the result of
// Object::operator coro::promise_type().
f2(Object{});

/permissive- y los módulos de C++20 están activados de forma predeterminada en /std:c++20

La compatibilidad con los módulos de C++20 está activada de forma predeterminada en /std:c++20 y /std:c++latest . Para más información sobre este cambio y los escenarios en los que module y import se tratan condicionalmente como palabras clave, consulte Compatibilidad con módulos de C++20 estándar con MSVC en Visual Studio 2019, versión 16.8.

Como requisito previo para la compatibilidad con los módulos, permissive- ahora se habilita cuando se especifica /std:c++20 o /std:c++latest . Para obtener más información, vea /permissive-.

Para el código que se compiló anteriormente en /std:c++latest y que requiere comportamientos del compilador que no cumplen las especificaciones, se puede indicar /permissive para desactivar el modo de cumplimiento estricto en el compilador. La opción del compilador debe aparecer después de /std:c++latest en la lista de argumentos de la línea de comandos. Sin embargo, /permissive genera un error si se detecta el uso de los módulos:

error C1214: Los módulos entran en conflicto con un comportamiento no estándar solicitado a través de "option".

Los valores más comunes para option son:

Opción Descripción
/Zc:twoPhase- Se requiere una búsqueda de nombres en dos fases para los módulos de C++ 20 e implícitos en /permissive- .
/Zc:hiddenFriend- Las reglas de búsqueda ocultas estándar de nombres descriptivos son necesarias para los módulos de C++ 20; además, /permissive- las indica de forma implícita.
/Zc:lambda- El procesamiento lambda estándar es necesario para los módulos de C++20 y está implícito en el modo /std:c++20 o posterior.
/Zc:preprocessor- El preprocesador compatible solo es necesario para el uso y la creación de unidades de encabezado de C++20. Los módulos con nombre no requieren esta opción.

Todavía se requiere la opción /experimental:module para usar los módulos std.* que se incluyen con Visual Studio, porque aún no están estandarizados.

La opción /experimental:module también implica /Zc:twoPhase, /Zc:lambda y /Zc:hiddenFriend. Anteriormente, el código compilado con los módulos a veces se podía compilar con /Zc:twoPhase- si el módulo solo se consumía. Este comportamiento ya no se admite.

Mejoras de cumplimiento en Visual Studio 2019, versión 16.9

Inicialización de copias de elementos temporales en la inicialización directa de referencias

El problema del grupo de trabajo principal CWG 2267 trató una incoherencia entre una lista de inicializadores entre paréntesis y una lista de inicializadores entre llaves. La resolución armoniza las dos formas.

Visual Studio 2019, versión 16.9, implementa el comportamiento cambiado en todos los modos del compilador /std . Sin embargo, dado que es potencialmente un cambio importante en el origen, solo se admite si el código se compila con /permissive- .

Este ejemplo muestra el cambio de comportamiento:

struct A { };

struct B {
    explicit B(const A&);
};

void f()
{
    A a;
    const B& b1(a);     // Always an error
    const B& b2{ a };   // Allowed before resolution to CWG 2267 was adopted: now an error
}

Características de los destructores y subobjetos potencialmente construidos

El problema del grupo de trabajo principal CWG 2336 cubre una omisión sobre las especificaciones de excepción implícita de los destructores en las clases que tienen clases base virtuales. La omisión significaba que un destructor de una clase derivada podía tener una especificación de excepción más débil que una clase base, si esa base era abstracta y tenía una base virtual.

Visual Studio 2019, versión 16.9, implementa el comportamiento cambiado en todos los modos del compilador /std .

Este ejemplo muestra cómo cambió la interpretación:

class V {
public:
    virtual ~V() noexcept(false);
};

class B : virtual V {
    virtual void foo () = 0;
    // BEFORE: implicitly defined virtual ~B() noexcept(true);
    // AFTER: implicitly defined virtual ~B() noexcept(false);
};

class D : B {
    virtual void foo ();
    // implicitly defined virtual ~D () noexcept(false);
};

Antes de este cambio, el destructor definido implícitamente para B era noexcept, porque solo se han tenido en cuenta los subobjetos creados potencialmente. Además, la clase base V no es un subobjeto potencialmente construido, porque es una base virtual y B es abstracto. Sin embargo, la clase base V es un subobjeto potencialmente construido de la clase D, por lo que se determina que D::~D es noexcept(false), dando lugar a una clase derivada con una especificación de excepción más débil que su base. Esta interpretación no es segura. Puede provocar un comportamiento incorrecto en el entorno de ejecución si se produce una excepción desde un destructor de una clase derivada de B.

Con este cambio, un destructor también puede generar excepciones si tiene un destructor virtual y cualquier clase base virtual tiene un destructor que puede generar excepciones.

Tipos y enlaces de referencia similares

El problema del grupo de trabajo principal CWG 2352 trata una incoherencia entre las reglas de enlace de referencia y los cambios en la similitud de los tipos. La incoherencia se presentó en los informes de defectos anteriores (como CWG 330). Afectó a las versiones 16.0 a 16.8 de Visual Studio 2019.

Con este cambio, a partir de la versión 16.9 de Visual Studio 2019, el código que antes enlazaba una referencia a un elemento temporal en las versiones 16.0 a 16.8 de Visual Studio 2019 ahora se puede enlazar directamente cuando los tipos implicados solo difieren en los calificadores CV.

Visual Studio 2019, versión 16.9, implementa el comportamiento cambiado en todos los modos del compilador /std . Es potencialmente un cambio importante en el origen.

Vea Referencias a tipos con calificadores CV discrepantes para consultar un cambio relacionado.

En este ejemplo se muestra el comportamiento con este cambio:

int *ptr;
const int *const &f() {
    return ptr; // Now returns a reference to 'ptr' directly.
    // Previously returned a reference to a temporary and emitted C4172
}

La actualización puede cambiar el comportamiento del programa que se basaba en un elemento temporal introducido:

int func() {
    int i1 = 13;
    int i2 = 23;
    
    int* iptr = &i1;
    int const * const&  iptrcref = iptr;

    // iptrcref is a reference to a pointer to i1 with value 13.
    if (*iptrcref != 13)
    {
        return 1;
    }
    
    // Now change what iptr points to.

    // Prior to CWG 2352 iptrcref should be bound to a temporary and still points to the value 13.
    // After CWG 2352 it is bound directly to iptr and now points to the value 23.
    iptr = &i2;
    if (*iptrcref != 23)
    {
        return 1;
    }

    return 0;
}

Cambio de comportamiento de las opciones /Zc:twoPhase y /Zc:twoPhase-

Normalmente, las opciones del compilador de MSVC funcionan según el principio de que el último gana. Desafortunadamente, este no fue el caso de las opciones /Zc:twoPhase y /Zc:twoPhase- . Estas opciones eran "permanentes", por lo que las opciones posteriores no podían reemplazarlas. Por ejemplo:

cl /Zc:twoPhase /permissive a.cpp

En este caso, la primera opción /Zc:twoPhase habilita la búsqueda estricta de nombres en dos fases. La segunda opción está pensada para deshabilitar el modo de cumplimiento estricto (es lo contrario de /permissive- ), pero no deshabilitó /Zc:twoPhase .

Visual Studio 2019, versión 16.9, cambia este comportamiento en todos los modos del compilador /std . /Zc:twoPhase y /Zc:twoPhase- ya no son "permanentes", y las opciones posteriores pueden reemplazarlos.

Especificadores explícitos noexcept en las plantillas de destructor

El compilador aceptaba previamente una plantilla de destructor declarada con una especificación de no inicio de excepciones, pero definida sin un especificador explícito noexcept. La especificación de la excepción implícita de un destructor depende de propiedades de la clase ; propiedades que pueden no ser conocidas en el punto de definición de una plantilla. El estándar C++ también requiere este comportamiento: si un destructor se declara sin un especificador noexcept, tiene una especificación de excepción implícita, y ninguna otra declaración de la función puede tener un especificador noexcept.

Visual Studio 2019, versión 16.9, cambia al comportamiento conforme en todos los modos del compilador /std .

En este ejemplo se muestra el cambio en el comportamiento del compilador:

template <typename T>
class B {
    virtual ~B() noexcept; // or throw()
};

template <typename T>
B<T>::~B() { /* ... */ } // Before: no diagnostic.
// Now diagnoses a definition mismatch. To fix, define the implementation by 
// using the same noexcept-specifier. For example,
// B<T>::~B() noexcept { /* ... */ }

Expresiones reescritas en C++ 20

Desde Visual Studio 2019, versión 16.2, en /std:c++latest , el compilador ha aceptado código como este ejemplo:

#include <compare>

struct S {
    auto operator<=>(const S&) const = default;
    operator bool() const;
};

bool f(S a, S b) {
    return a < b;
}

Sin embargo, el compilador no invocaría la función de comparación que el autor podría esperar. El código anterior debería haber reescrito a < b como (a <=> b) < 0. En su lugar, el compilador usaba la función de conversión definida por el usuario operator bool() y comparaba bool(a) < bool(b). En Visual Studio 2019, versión 16.9 y posteriores, el compilador vuelve a escribir la expresión utilizando la expresión del operador spaceship esperada.

Cambios importantes del código fuente

Aplicar correctamente las conversiones a las expresiones reescritas tiene otro efecto: el compilador también diagnostica correctamente las ambigüedades de los intentos de volver a escribir la expresión. Considere este ejemplo:

struct Base {
    bool operator==(const Base&) const;
};

struct Derived : Base {
    Derived();
    Derived(const Base&);
    bool operator==(const Derived& rhs) const;
};

bool b = Base{} == Derived{};

En C++17, este código se aceptaría debido a la conversión de derivado a base de Derived en el lado derecho de la expresión. En C++ 20, el candidato de expresión sintetizada también se agrega: Derived{} == Base{}. Debido a las reglas del estándar sobre qué función gana según las conversiones, resulta que la elección entre Base::operator== y Derived::operator== no se puede resolver. Como las secuencias de conversión de las dos expresiones no son mejores ni peores unas que otras, el código de ejemplo produce una ambigüedad.

Para resolver la ambigüedad, agregue un nuevo candidato que no esté sujeto a las dos secuencias de conversión:

bool operator==(const Derived&, const Base&);

Cambio importante en el entorno de ejecución

Debido a las reglas de reescritura de operadores en C++20, es posible que la resolución de sobrecargas encuentre un nuevo candidato que no encontraría en un modo de lenguaje inferior. Además, el nuevo candidato puede ser una mejor coincidencia que el candidato anterior. Considere este ejemplo:

struct iterator;
struct const_iterator {
  const_iterator(const iterator&);
  bool operator==(const const_iterator &ci) const;
};

struct iterator {
  bool operator==(const const_iterator &ci) const { return ci == *this; }
};

En C++ 17, el único candidato para ci == *this es const_iterator::operator==. Es una coincidencia porque *this pasa por una conversión de derivación a base a const_iterator. En C++ 20, se agrega otro candidato reescrito, *this == ci, que invoca a iterator::operator==. Este candidato no requiere conversiones, por lo que es una coincidencia mejor que const_iterator::operator==. El problema con el nuevo candidato es que es la función que se está definiendo actualmente, por lo que la nueva semántica de la función provoca una definición infinitamente recursiva de iterator::operator==.

Para ayudar en código como el del ejemplo, el compilador implementa una nueva advertencia:

$ cl /std:c++latest /c t.cpp
t.cpp
t.cpp(8): warning C5232: in C++20 this comparison calls 'bool iterator::operator ==(const const_iterator &) const' recursively

Para corregir el código, sea explícito sobre la conversión que se va a usar:

struct iterator {
  bool operator==(const const_iterator &ci) const { return ci == static_cast<const const_iterator&>(*this); }
};

Mejoras de cumplimiento en Visual Studio 2019, versión 16.10

Sobrecarga incorrecta elegida para la inicialización de copia de la clase

Con este código de ejemplo:

struct A { template <typename T> A(T&&); };
struct B { operator A(); };
struct C : public B{};
void f(A);
f(C{});

Las versiones anteriores del compilador convertirían incorrectamente el argumento de f del tipo C a A con el constructor de conversión mediante plantilla de A. El estándar C++ requiere el uso del operador de conversión B::operator A en su lugar. En Visual Studio 2019, versión 16.10 y posteriores, se cambia el comportamiento de resolución de sobrecarga para usar la sobrecarga correcta.

Este cambio también puede corregir la sobrecarga elegida en otras situaciones:

struct Base 
{
    operator char *();
};

struct Derived : public Base
{
    operator bool() const;
};

void f(Derived &d)
{
    // Implicit conversion to bool previously used Derived::operator bool(), now uses Base::operator char*.
    // The Base function is preferred because operator bool() is declared 'const' and requires a qualification
    // adjustment for the implicit object parameter, while the Base function does not.
    if (d)
    {
        // ...
    }
}

Análisis incorrecto de literales de punto flotante

En Visual Studio 2019, versión 16.10 y posteriores, los literales de punto flotante se analizan en función de su tipo real. Las versiones anteriores del compilador analizaban siempre un literal de punto flotante como si tuviera el tipo double y, a continuación, convertían el resultado en el tipo real. Este comportamiento podría provocar un redondeo incorrecto y el rechazo de valores válidos:

// The binary representation is '0x15AE43FE' in VS2019 16.9
// The binary representation is '0x15AE43FD' in VS2019 16.10
// You can use 'static_cast<float>(7.038531E-26)' if you want the old behavior.
float f = 7.038531E-26f;

Punto de declaración incorrecto

Las versiones anteriores del compilador no podían compilar código autoferencial como en este ejemplo:

struct S {
    S(int, const S*);

    int value() const;
};

S s(4, &s);

El compilador no declararía la variable s hasta que analizara toda la declaración, incluidos los argumentos del constructor. Se produciría un error en la búsqueda de s en la lista de argumentos del constructor. En Visual Studio 2019, versión 16.10 y posteriores, este ejemplo ahora se compila correctamente.

Lamentablemente, este cambio puede afectar al código existente, como en este ejemplo:

S s(1, nullptr); // outer s
// ...
{
   S s(s.value(), nullptr); // inner s
}

En versiones anteriores del compilador, cuando busca s en los argumentos del constructor de la declaración "interna" de s, busca la declaración anterior (s "externa") y el código se compila. A partir de la versión 16.10, el compilador emite la advertencia C4700 en su lugar. Esto se debe a que el compilador declara ahora el s "interno" antes de analizar los argumentos del constructor. Por tanto, la búsqueda de s detecta el s "interno", que aún no se ha inicializado.

Miembro especializado explícitamente de una plantilla de clase

Las versiones anteriores del compilador marcaban incorrectamente una especialización explícita de un miembro de plantilla de clase como inline si también se definió en la plantilla principal. Este comportamiento conllevaba que el compilador a veces rechazara el código compatible. En Visual Studio 2019, versión 16.10 y posteriores, una especialización explícita ya no se marca implícitamente como inline en el modo /permissive-. Considere este ejemplo:

Archivo de origen s.h:

// s.h
template<typename T>
struct S {
    int f() { return 1; }
};
template<> int S<int>::f() { return 2; }

Archivo de origen s.cpp:

// s.cpp
#include "s.h"

Archivo de origen main.cpp:

// main.cpp
#include "s.h"

int main()
{
}

Para solucionar el error del vinculador en el ejemplo anterior, agregue inline explícitamente a S<int>::f:

template<> inline int S<int>::f() { return 2; }

Eliminación de nombres de tipo de valor devuelto deducido

En Visual Studio 2019, versión 16.10 y posteriores, el compilador cambió la forma en que genera nombres eliminados para las funciones que han deducido tipos de valor devuelto. Por ejemplo, considere estas funciones:

auto f() { return 0; }
auto g() { []{}; return 0; }

Las versiones anteriores del compilador generarían estos nombres para el vinculador:

f: ?f@@YAHXZ -> int __cdecl f(void)
g: ?g@@YA@XZ -> __cdecl g(void)

Sorprendentemente, el tipo de valor devuelto se omitiría en g debido a otro comportamiento semántico causado por la expresión lambda local en el cuerpo de la función. Esta incoherencia dificultaba la implementación de funciones exportadas que tienen un tipo de valor devuelto deducido: la interfaz del módulo requiere información sobre cómo se compiló el cuerpo de una función. Necesita la información para generar una función en el lado de importación que pueda vincularse correctamente a la definición.

El compilador ahora omite el tipo de valor devuelto de una función de tipo de valor devuelto deducido. Este comportamiento es coherente con otras implementaciones principales. Hay una excepción para las plantillas de función: esta versión del compilador presenta un nuevo comportamiento de nombre eliminado para las plantillas de función que tienen un tipo de valor devuelto deducido:

template <typename T>
auto f(T) { return 1; }

template <typename T>
decltype(auto) g(T) { return 1.; }

int (*fp1)(int) = &f;
double (*fp2)(int) = &g;

Los nombres eliminados de auto y decltype(auto) ahora aparecen en el archivo binario, no en el tipo de valor devuelto deducido:

f: ??$f@H@@YA?A_PH@Z -> auto __cdecl f<int>(int)
g: ??$g@H@@YA?A_TH@Z -> decltype(auto) __cdecl g<int>(int)

Las versiones anteriores del compilador incluirían el tipo de valor devuelto deducido como parte de la firma. Cuando el compilador incluía el tipo de valor devuelto en el nombre eliminado, podía provocar problemas del vinculador. Algunos escenarios bien formados de cualquier otro modo serían ambiguos para el vinculador.

El nuevo comportamiento del compilador puede producir un cambio importante en los archivos binarios. Considere este ejemplo:

Archivo de origen a.cpp:

// a.cpp
auto f() { return 1; }

Archivo de origen main.cpp:

// main.cpp
int f();
int main() { f(); }

En versiones anteriores a la versión 16.10, el compilador generaba un nombre para auto f() parecido a int f(), aunque son funciones semánticamente distintas. Esto significa que el ejemplo se compilaría. Para corregir el problema, no se base en auto en la definición original de f. En su lugar, escríbalo como int f(). Dado que las funciones que han deducido tipos de valor devuelto siempre se compilan, se minimizan las implicaciones de ABI.

Advertencia para el atributo nodiscard ignorado

Las versiones anteriores del compilador omitían silenciosamente determinados usos de un atributo nodiscard. Ignoraban el atributo si estaba en una posición sintáctica que no se aplicaba a la función o clase que se declaraba. Por ejemplo:

static [[nodiscard]] int f() { return 1; }

En Visual Studio 2019, versión 16.10 y posteriores, el compilador emite en su lugar la advertencia C5240 de nivel 4:

a.cpp(1): warning C5240: 'nodiscard': attribute is ignored in this syntactic position

Para corregir este problema, mueva el atributo a la posición sintáctica correcta:

[[nodiscard]] static int f() { return 1; }

Advertencia para las directivas include con nombres de encabezado del sistema en la versión Purview del módulo

En Visual Studio 2019, versión 16.10 y posteriores, el compilador emite una advertencia para evitar un error común de creación de la interfaz del módulo. Si incluye un encabezado de biblioteca estándar después de una instrucción export module, el compilador emite la advertencia C5244. Este es un ejemplo:

export module m;
#include <vector>

export
void f(std::vector<int>);

Es probable que el desarrollador no pretendiera que el módulo m incluyera el contenido de <vector>. El compilador ahora emite una advertencia para ayudar a encontrar y corregir el problema:

m.ixx(2): warning C5244: '#include <vector>' in the purview of module 'm' appears erroneous. Consider moving that directive before the module declaration, or replace the textual inclusion with an "import <vector>;".
m.ixx(1): note: see module 'm' declaration

Para corregir este problema, mueva #include <vector> antes de export module m;:

#include <vector>
export module m;

export
void f(std::vector<int>);

Advertencia para las funciones de vinculación internas no utilizadas

En Visual Studio 2019, versión 16.10 y posteriores, el compilador genera advertencias en más situaciones en las que se ha quitado una función sin referencia con vinculación interna. Las versiones anteriores del compilador emitían la advertencia C4505 para el código siguiente:

static void f() // warning C4505: 'f': unreferenced function with internal linkage has been removed
{
}

El compilador ahora también advierte sobre las funciones auto sin referencia y las funciones sin referencia en espacios de nombres anónimos. Emite una advertencia C5245 desactivada de forma predeterminada para las dos funciones siguientes:

namespace
{
    void f1() // warning C5245: '`anonymous-namespace'::f1': unreferenced function with internal linkage has been removed
    {
    }
}

auto f2() // warning C5245: 'f2': unreferenced function with internal linkage has been removed
{
    return []{ return 13; };
}

Advertencia sobre omisión de llaves

En Visual Studio 2019, versión 16.10 y posteriores, el compilador advierte sobre las listas de inicialización que no usan llaves para los subobjetos. El compilador emite la advertencia C5246 desactivada de forma predeterminada.

Este es un ejemplo:

struct S1 {
  int i, j;
};

struct S2 {
   S1 s1;
   int k;
};

S2 s2{ 1, 2, 3 }; // warning C5246: 'S2::s1': the initialization of a subobject should be wrapped in braces

Para corregir este problema, encierre la inicialización del subobjeto entre llaves:

S2 s2{ { 1, 2 }, 3 };

Detección correcta si un objeto const no se inicializa

En Visual Studio 2019, versión 16.10 y posteriores, el compilador ahora emite el error C2737 al intentar definir un objeto const que no está totalmente inicializado:

struct S {
   int i;
   int j = 2;
};

const S s; // error C2737: 's': const object must be initialized

Las versiones anteriores del compilador permitían que este código se compilara, aunque S::i no se inicializase.

Para corregir este problema, inicialice todos los miembros antes de crear una instancia const de un objeto:

struct S {
   int i = 1;
   int j = 2;
};

Mejoras de cumplimiento en Visual Studio 2019, versión 16.11

Modo de compilador de /std:c++20

En Visual Studio 2019, versión 16.11 y posteriores, el compilador ahora admite el modo de compilador /std:c++20. Anteriormente, las características de C++20 solo estaban disponibles en el modo /std:c++latest de Visual Studio 2019. Las características de C++20 que originalmente requerían el modo /std:c++latest ahora funcionan en el modo /std:c++20 o posterior en las versiones más recientes de Visual Studio.

Vea también

Conformidad del lenguaje Microsoft C/C++