/Zc:ternary (Exigir reglas de operador condicionales)

Habilite la aplicación de reglas estándar de C++ para los tipos y la calificación const o volátil (cv) del segundo y tercer operando en una expresión de operador condicional.

Sintaxis

/Zc:ternary[-]

Comentarios

A partir de Visual Studio 2017, el compilador admite el comportamiento del operador condicional estándar (?:) de C++. También se conoce como operador ternario. El estándar de C++ requiere que los operandos ternarios cumplan una de estas tres condiciones: los operandos deben ser del mismo tipo const y o volatile cualificación (cv-cualificación), o solo un operando debe poder convertirse de forma inequívoca en el mismo tipo y cv-cualificación que el otro. O bien, uno o ambos operandos deben ser una expresión throw. En versiones anteriores a Visual Studio 2017 versión 15.5, el compilador permitía conversiones que el estándar considera ambigua.

Cuando se especifica la opción /Zc:ternary, el compilador se ajusta al estándar. Rechaza el código que no cumple las reglas para los tipos coincidentes y la calificación cv del segundo y tercer operando.

Esta opción /Zc:ternary es la predeterminada en Visual Studio 2017. Use /Zc:ternary para habilitar el comportamiento conforme o /Zc:ternary- para especificar explícitamente el comportamiento anterior del compilador no conforme. La opción /permissive- habilita implícitamente esta opción, pero se puede invalidar mediante /Zc:ternary-.

Ejemplos

En este ejemplo se muestra cómo una clase que proporciona una inicialización no explícita de un tipo y la conversión a un tipo puede dar lugar a conversiones ambiguas. El compilador acepta este código de forma predeterminada, pero se rechaza cuando se especifica /Zc:ternary o /permissive-.

// zcternary1.cpp
// Compile by using: cl /EHsc /W4 /nologo /Zc:ternary zcternary1.cpp

struct A
{
   long l;
   A(int i) : l{i} {}    // explicit prevents conversion of int
   operator int() const { return static_cast<int>(l); }
};

int main()
{
   A a(42);
   // Accepted when /Zc:ternary (or /permissive-) is not used
   auto x = true ? 7 : a;  // old behavior prefers A(7) over (int)a
   auto y = true ? A(7) : a;   // always accepted
   auto z = true ? 7 : (int)a; // always accepted
   return x + y + z;
}

Para corregir este código, realice una conversión explícita al tipo común preferido o evite una dirección de conversión de tipo. Puede evitar que el compilador coincida con una conversión de tipos haciendo explícita la conversión.

Una excepción importante a este patrón común es cuando el tipo de operandos es uno de los tipos de cadena terminados en null, como const char*, const char16_t*, etc. También puede reproducir el efecto con tipos de matriz y los tipos de puntero a los que se desintegran. El comportamiento cuando el segundo o tercer operando real a ?: es un literal de cadena del tipo correspondiente depende del estándar de lenguaje utilizado. C++17 ha cambiado la semántica para este caso de C++14. Como resultado, el compilador acepta el código en el ejemplo siguiente en el valor predeterminado /std:c++14, pero lo rechaza cuando se especifica /std:c++17 o posterior.

// zcternary2.cpp
// Compile by using: cl /EHsc /W4 /nologo /Zc:ternary /std:c++17 zcternary2.cpp

struct MyString
{
   const char * p;
   MyString(const char* s = "") noexcept : p{s} {} // from char*
   operator const char*() const noexcept { return p; } // to char*
};

int main()
{
   MyString s;
   auto x = true ? "A" : s; // MyString: permissive prefers MyString("A") over (const char*)s
}

Para corregir este código, convierta uno de los operandos explícitamente.

En /Zc:ternary, el compilador rechaza los operadores condicionales donde uno de los argumentos es de tipo void y el otro no es una expresión throw. Un uso común de este patrón se encuentra en macros similares a ASSERT:

// zcternary3.cpp
// Compile by using: cl /EHsc /W4 /nologo /Zc:ternary /c zcternary3.cpp

void myassert(const char* text, const char* file, int line);
#define ASSERT(ex) (void)((ex) ? 0 : myassert(#ex, __FILE__, __LINE__))
// To fix, define it this way instead:
// #define ASSERT(ex) (void)((ex) ? void() : myassert(#ex, __FILE__, __LINE__))

int main()
{
   ASSERT(false);  // C3447
}

La solución típica consiste en reemplazar el argumento no void por void().

En este ejemplo se muestra el código que genera un error en /Zc:ternary y /Zc:ternary-:

// zcternary4.cpp
// Compile by using:
//   cl /EHsc /W4 /nologo /Zc:ternary zcternary4.cpp
//   cl /EHsc /W4 /nologo /Zc:ternary zcternary4.cpp

int main() {
   auto p1 = [](int a, int b) { return a > b; };
   auto p2 = [](int a, int b) { return a > b; };
   auto p3 = true ? p1 : p2; // C2593 under /Zc:ternary, was C2446
}

Este código dio anteriormente este error:

error C2446: ':': no conversion from 'foo::<lambda_f6cd18702c42f6cd636bfee362b37033>' to 'foo::<lambda_717fca3fc65510deea10bc47e2b06be4>'
note: No user-defined-conversion operator available that can perform this conversion, or the operator cannot be called

Con /Zc:ternary, el motivo del error se vuelve más claro. Cualquiera de las convenciones de llamada definidas por la implementación se podría usar para generar cada expresión lambda. Sin embargo, el compilador no tiene ninguna regla de preferencia para eliminar la ambigüedad de las posibles firmas lambda. La nueva salida tiene el siguiente aspecto:

error C2593: 'operator ?' is ambiguous
note: could be 'built-in C++ operator?(bool (__cdecl *)(int,int), bool (__cdecl *)(int,int))'
note: or       'built-in C++ operator?(bool (__stdcall *)(int,int), bool (__stdcall *)(int,int))'
note: or       'built-in C++ operator?(bool (__fastcall *)(int,int), bool (__fastcall *)(int,int))'
note: or       'built-in C++ operator?(bool (__vectorcall *)(int,int), bool (__vectorcall *)(int,int))'
note: while trying to match the argument list '(foo::<lambda_717fca3fc65510deea10bc47e2b06be4>, foo::<lambda_f6cd18702c42f6cd636bfee362b37033>)'

Un origen común de problemas encontrados por /Zc:ternary procede de operadores condicionales usados en la meta-programación de plantillas. Algunos de los tipos de resultado cambian en este modificador. En el ejemplo siguiente se muestran dos casos en los que /Zc:ternary cambia el tipo de resultado de una expresión condicional en un contexto que no es de programación meta:

// zcternary5.cpp
// Compile by using: cl /EHsc /W4 /nologo /Zc:ternary zcternary5.cpp

int main(int argc, char**) {
   char a = 'A';
   const char b = 'B';
   decltype(auto) x = true ? a : b; // char without, const char& with /Zc:ternary
   const char(&z)[2] = argc > 3 ? "A" : "B"; // const char* without /Zc:ternary
   return x > *z;
}

La corrección típica es aplicar un rasgo std::remove_reference en el tipo de resultado, donde sea necesario para conservar el comportamiento anterior.

Para obtener más información sobre los problemas de conformidad de Visual C++, vea Nonstandard Behavior.

Para establecer esta opción del compilador en el entorno de desarrollo de Visual Studio

  1. Abra el cuadro de diálogo Páginas de propiedades del proyecto. Para más información, vea Establecimiento del compilador de C++ y de propiedades de compilación en Visual Studio.

  2. Seleccione la página de propiedades Propiedades de configuración>C/C++>Línea de comandos.

  3. Modifique la propiedad Opciones adicionales para que incluya /Zc:ternary o /Zc:ternary- y, después, seleccione Aceptar.

Consulte también

/Zc (Conformidad)