/Zc:ternary (Принудительное применение правил условного оператора)

Включите принудительное применение стандартных правил C++ для типов и константной или переменной (cv) квалификации второго и третьего операндов в выражении условного оператора.

Синтаксис

/Zc:ternary[-]

Замечания

Начиная с Visual Studio 2017 компилятор поддерживает стандартное поведение условного оператора?: C++. Он также называется тернарным оператором. Стандарт C++ требует, чтобы тернарные операнды соответствовали одному из трех условий: операнды должны быть одного типа и квалификации volatile (cv-квалификации), или только один операнды должны быть однозначно преобразованы в тот же тип и const квалификацию cv-квалификации, что и другая. Или один или оба операнда должны быть выражением броска. В версиях до Visual Studio 2017 версии 15.5 компилятор допускает преобразования, которые считаются неоднозначными по стандарту.

/Zc:ternary При указании параметра компилятор соответствует стандарту. Он отклоняет код, который не удовлетворяет правилам для соответствующих типов и квалификации cv-квалификации второго и третьего операндов.

Параметр /Zc:ternary отключен по умолчанию в Visual Studio 2017. Используется /Zc:ternary для включения поведения соответствия или /Zc:ternary- явного указания предыдущего несоответствующего поведения компилятора. Параметр /permissive- неявно включает этот параметр, но его можно переопределить с помощью /Zc:ternary-.

Примеры

В этом примере показано, как класс, предоставляющий как неясную инициализацию из типа, так и преобразование в тип, может привести к неоднозначным преобразованиям. Этот код принимается компилятором по умолчанию, но отклоняется при /Zc:ternary указании или /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;
}

Чтобы исправить этот код, сделайте явный приведение к предпочтительному общему типу или запретите одно направление преобразования типов. Компилятор может не соответствовать преобразованию типов, сделав преобразование явным.

Важное исключение из этого общего шаблона заключается в том, что тип операндов является одним из типов строк, завершаемых значением NULL, например const char*, const char16_t*и т. д. Вы также можете воспроизвести эффект с типами массивов и типами указателей, на которые они распадаются. Поведение, когда фактический второй или третий операнд ?: является строковым литералом соответствующего типа, зависит от используемого языкового стандарта. C++17 изменил семантику для этого случая с C++14. В результате компилятор принимает код в следующем примере по умолчанию /std:c++14, но отклоняет его при указании /std:c++17 или более поздней версии.

// 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
}

Чтобы исправить этот код, явно приведение одного из операндов.

В разделе /Zc:ternaryкомпилятор отклоняет условные операторы, в которых один из аргументов имеет тип void, а другой — не throw выражение. Обычное использование этого шаблона используется в макросах, таких как 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
}

Обычное решение — заменить аргумент void()непустой на .

В этом примере показан код, который создает ошибку в обоих /Zc:ternary и /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
}

Этот код ранее дал эту ошибку:

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

При этом /Zc:ternaryпричина сбоя становится ясной. Для создания каждой лямбда-лямбда-соглашения можно использовать любое из определенных реализацией соглашений о вызовах. Однако компилятор не имеет правила предпочтения для диамбигуации возможных лямбда-подписей. Новые выходные данные выглядят следующим образом:

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>)'

Распространенный источник проблем, найденных /Zc:ternary из условных операторов, используемых в метапрограмме шаблона. Некоторые типы результатов изменяются под этим параметром. В следующем примере демонстрируется два случая /Zc:ternary изменения типа результата условного выражения в контексте, отличном от метапрограммного программирования:

// 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;
}

Обычное исправление заключается в применении std::remove_reference признака к типу результата, где необходимо сохранить старое поведение.

Дополнительные сведения о вопросах соответствия в Visual C++ см. в статье Nonstandard Behavior.

Установка данного параметра компилятора в среде разработки Visual Studio

  1. Откройте диалоговое окно Страницы свойств проекта. Подробнее см. в статье Настройка компилятора C++ и свойства сборки в Visual Studio.

  2. Перейдите на страницу свойств Свойства конфигурации>C/C++>Командная строка.

  3. Измените свойство "Дополнительные параметры", чтобы включить /Zc:ternary или /Zc:ternary- нажмите кнопку "ОК".

См. также

/Zc (Соответствие)