MSVC información general sobre el nuevo preprocesador
Visual Studio 2015 usa el preprocesador tradicional, que no se ajusta a C++ estándar o C99. A partir Visual Studio versión 16.5 de 2019, la nueva compatibilidad con el preprocesador para el estándar C++20 está completa. Estos cambios están disponibles mediante el modificador del compilador /Zc:preprocessor. Hay disponible una versión experimental del nuevo preprocesador a partir de la versión 15.8 de Visual Studio 2017 y versiones posteriores mediante el modificador del compilador /experimental:preprocessor. Puede encontrar más información sobre el uso del nuevo preprocesador Visual Studio 2017 y Visual Studio 2019. Para ver la documentación de su versión preferida de Visual Studio, use el control de selector Versión. Se encuentra en la parte superior de la tabla de contenido de esta página.
Estamos actualizando el preprocesador de Microsoft C++ para mejorar la conformidad con los estándares, corregir errores de larga duración y cambiar algunos comportamientos que oficialmente no están definidos. También hemos agregado nuevos diagnósticos para advertir sobre errores en las definiciones de macro.
A partir Visual Studio versión 16.5 de 2019, la compatibilidad con el preprocesador para el estándar C++20 está completa. Estos cambios están disponibles mediante el modificador del compilador /Zc:preprocessor. Hay disponible una versión experimental del nuevo preprocesador en versiones anteriores a partir de Visual Studio versión 15.8 de 2017. Puede habilitarlo mediante el modificador del compilador /experimental:preprocessor. El comportamiento predeterminado del preprocesador sigue siendo el mismo que en versiones anteriores.
Nueva macro predefinida
Puede detectar qué preprocesador está en uso en tiempo de compilación. Compruebe el valor de la macro predefinida _MSVC_TRADITIONAL para saber si el preprocesador tradicional está en uso. Esta macro se establece incondicionalmente por las versiones del compilador que la admiten, independientemente del preprocesador que se invoque. Su valor es 1 para el preprocesador tradicional. Es 0 para el preprocesador conforme.
#if defined(_MSVC_TRADITIONAL) && _MSVC_TRADITIONAL
// Logic using the traditional preprocessor
#else
// Logic using cross-platform compatible preprocessor
#endif
Cambios de comportamiento en el nuevo preprocesador
El trabajo inicial en el nuevo preprocesador se ha centrado en hacer que todas las expansiones de macro se ajusten al estándar. Permite usar el compilador MSVC con bibliotecas que actualmente están bloqueadas por los comportamientos tradicionales. Hemos probado el preprocesador actualizado en proyectos del mundo real. Estos son algunos de los cambios importantes más comunes que hemos encontrado:
Comentarios de macro
El preprocesador tradicional se basa en búferes de caracteres en lugar de tokens de preprocesador. Permite un comportamiento inusual, como el siguiente truco de comentario de preprocesador, que no funciona con el preprocesador conforme:
#if DISAPPEAR
#define DISAPPEARING_TYPE /##/
#else
#define DISAPPEARING_TYPE int
#endif
// myVal disappears when DISAPPEARING_TYPE is turned into a comment
DISAPPEARING_TYPE myVal;
La corrección conforme a los estándares es declarar int myVal dentro de las directivas #ifdef/#endif adecuadas:
#define MYVAL 1
#ifdef MYVAL
int myVal;
#endif
L#val
El preprocesador tradicional combina incorrectamente un prefijo de cadena con el resultado del operador de cadenas (#):
#define DEBUG_INFO(val) L"debug prefix:" L#val
// ^
// this prefix
const wchar_t *info = DEBUG_INFO(hello world);
En este caso, el prefijo no es necesario porque los literales de cadena adyacentes se combinan después de la expansión de macro de L todos modos. La corrección compatible con versiones anteriores es cambiar la definición:
#define DEBUG_INFO(val) L"debug prefix:" #val
// ^
// no prefix
El mismo problema también se encuentra en las macros de conveniencia que "stringizen" el argumento en un literal de cadena ancho:
// The traditional preprocessor creates a single wide string literal token
#define STRING(str) L#str
Puede corregir el problema de varias maneras:
Use la concatenación de cadenas
L""de y para agregar#strprefijo. Los literales de cadena adyacentes se combinan después de la expansión de macros:#define STRING1(str) L""#strAgregue el prefijo después de
#strque esté en cadena con expansión de macro adicional.#define WIDE(str) L##str #define STRING2(str) WIDE(#str)Use el operador de concatenación
##para combinar los tokens. El orden de las operaciones de y no se especifica, aunque todos los compiladores parecen evaluar##el operador antes en este####caso.#define STRING3(str) L## #str
Advertencia sobre no válido ##
Cuando el operador de pegar tokens (##) no da como resultado un único token de preprocesamiento válido, el comportamiento es indefinido. El preprocesador tradicional no puede combinar los tokens de forma silenciosa. El nuevo preprocesador coincide con el comportamiento de la mayoría de los demás compiladores y emite un diagnóstico.
// The ## is unnecessary and does not result in a single preprocessing token.
#define ADD_STD(x) std::##x
// Declare a std::string
ADD_STD(string) s;
Elisión de comas en macros variádicas
El preprocesador MSVC elimina siempre las comas antes de los __VA_ARGS__ reemplazos vacíos. El nuevo preprocesador sigue más de cerca el comportamiento de otros compiladores multiplataforma populares. Para que se quite la coma, debe faltar el argumento variádico (no solo vacío) y debe marcarse con un ## operador . Considere el ejemplo siguiente:
void func(int, int = 2, int = 3);
// This macro replacement list has a comma followed by __VA_ARGS__
#define FUNC(a, ...) func(a, __VA_ARGS__)
int main()
{
// In the traditional preprocessor, the
// following macro is replaced with:
// func(10,20,30)
FUNC(10, 20, 30);
// A conforming preprocessor replaces the
// following macro with: func(1, ), which
// results in a syntax error.
FUNC(1, );
}
En el ejemplo siguiente, en la llamada al FUNC2(1) argumento variádico falta en la macro que se invoca. En la llamada al FUNC2(1, ) argumento variádico está vacío, pero no falta (observe la coma en la lista de argumentos).
#define FUNC2(a, ...) func(a , ## __VA_ARGS__)
int main()
{
// Expands to func(1)
FUNC2(1);
// Expands to func(1, )
FUNC2(1, );
}
En el próximo estándar de C++20, este problema se ha solucionado agregando __VA_OPT__ . La nueva compatibilidad con el preprocesador para está disponible a partir __VA_OPT__ Visual Studio versión 16.5 de 2019.
Extensión de macro variádica de C++20
El nuevo preprocesador admite elisión de argumentos de macro variádicos de C++20:
#define FUNC(a, ...) __VA_ARGS__ + a
int main()
{
int ret = FUNC(0);
return ret;
}
Este código no se ajusta al estándar de C++20. En MSVC, el nuevo preprocesador extiende este comportamiento de C++20 a los modos estándar de lenguaje inferior ( /std:c++14 , /std:c++17 ). Esta extensión coincide con el comportamiento de otros compiladores de C++ multiplataforma principales.
Los argumentos de macro están "desempaquetados"
En el preprocesador tradicional, si una macro reenvía uno de sus argumentos a otra macro dependiente, el argumento no se "desempaquete" cuando se inserta. Normalmente, esta optimización pasa desapercibida, pero puede provocar un comportamiento inusual:
// Create a string out of the first argument, and the rest of the arguments.
#define TWO_STRINGS( first, ... ) #first, #__VA_ARGS__
#define A( ... ) TWO_STRINGS(__VA_ARGS__)
const char* c[2] = { A(1, 2) };
// Conforming preprocessor results:
// const char c[2] = { "1", "2" };
// Traditional preprocessor results, all arguments are in the first string:
// const char c[2] = { "1, 2", };
Al expandir , el preprocesador tradicional reenvía todos los argumentos empaquetados en al primer argumento de TWO_STRINGS, lo que deja vacío el argumento A()__VA_ARGS__TWO_STRINGS variádico de . Esto hace que el resultado #first de sea "1, 2" en lugar de simplemente "1". Si está siguiendo con atención, es posible que se pregunte qué ha ocurrido con el resultado de en la expansión tradicional del preprocesador: si el parámetro variádico está vacío, debería dar lugar a un literal de cadena #__VA_ARGS__"" vacío. Un problema independiente ha hecho que no se genere el token literal de cadena vacío.
Volver a examinar la lista de reemplazo de macros
Después de reemplazar una macro, los tokens resultantes se examinan de nuevo en busca de identificadores de macro adicionales que reemplazar. El algoritmo utilizado por el preprocesador tradicional para realizar el nuevo análisis no se ajusta, como se muestra en este ejemplo basado en código real:
#define CAT(a,b) a ## b
#define ECHO(...) __VA_ARGS__
// IMPL1 and IMPL2 are implementation details
#define IMPL1(prefix,value) do_thing_one( prefix, value)
#define IMPL2(prefix,value) do_thing_two( prefix, value)
// MACRO chooses the expansion behavior based on the value passed to macro_switch
#define DO_THING(macro_switch, b) CAT(IMPL, macro_switch) ECHO(( "Hello", b))
DO_THING(1, "World");
// Traditional preprocessor:
// do_thing_one( "Hello", "World");
// Conforming preprocessor:
// IMPL1 ( "Hello","World");
Aunque este ejemplo puede parecer un poco contrived, lo hemos visto en código real.
Para ver lo que sucede, podemos dividir la expansión a partir de DO_THING :
DO_THING(1, "World")se expande aCAT(IMPL, 1) ECHO(("Hello", "World"))CAT(IMPL, 1)se expandeIMPL ## 1a , que se expande aIMPL1- Ahora los tokens están en este estado:
IMPL1 ECHO(("Hello", "World")) - El preprocesador busca el identificador de macro de tipo función
IMPL1. Puesto que no va seguido de , no se considera una invocación de macro de tipo(función. - El preprocesador pasa a los siguientes tokens. Busca que se invoca la macro de tipo
ECHOfunción:ECHO(("Hello", "World")), que se expande a .("Hello", "World") IMPL1nunca se vuelve a considerar para la expansión, por lo que el resultado completo de las expansiones es:IMPL1("Hello", "World");
Para modificar la macro para que se comporte de la misma manera tanto en el nuevo preprocesador como en el preprocesador tradicional, agregue otra capa de direccionamiento indirecto:
#define CAT(a,b) a##b
#define ECHO(...) __VA_ARGS__
// IMPL1 and IMPL2 are macros implementation details
#define IMPL1(prefix,value) do_thing_one( prefix, value)
#define IMPL2(prefix,value) do_thing_two( prefix, value)
#define CALL(macroName, args) macroName args
#define DO_THING_FIXED(a,b) CALL( CAT(IMPL, a), ECHO(( "Hello",b)))
DO_THING_FIXED(1, "World");
// macro expands to:
// do_thing_one( "Hello", "World");
Características incompletas anteriores a la 16.5
A partir Visual Studio versión 16.5 de 2019, el nuevo preprocesador está completo para C++20. En versiones anteriores de Visual Studio, el nuevo preprocesador está principalmente completo, aunque alguna lógica de directiva de preprocesador todavía vuelve al comportamiento tradicional. Esta es una lista parcial de características incompletas en Visual Studio versiones anteriores a la 16.5:
- Compatibilidad con
_Pragma - Características de C++20
- Error de bloqueo de aumento: los operadores lógicos de expresiones constantes de preprocesador no se implementan completamente en el nuevo preprocesador antes de la versión 16.5. En algunas
#ifdirectivas, el nuevo preprocesador puede volver al preprocesador tradicional. El efecto solo es perceptible cuando se expanden las macros incompatibles con el preprocesador tradicional. Puede ocurrir al compilar ranuras de preprocesador Boost.