/Zc:ternary (Wymuszanie reguł operatorów warunkowych)

Włącz wymuszanie reguł standardowych języka C++ dla typów i kwalifikacji const lub volatile (cv) drugiego i trzeciego operandów w wyrażeniu operatora warunkowego.

Składnia

/Zc:ternary[-]

Uwagi

Począwszy od programu Visual Studio 2017, kompilator obsługuje zachowanie standardowego operatora warunkowego (?:) języka C++. Jest on również znany jako operatorternary. Standard C++ wymagaternarnych operandów spełniających jeden z trzech warunków: Operandy muszą być tego samego typu i constvolatile kwalifikacji (kwalifikacja cv), albo tylko jeden operand musi być jednoznacznie konwertowany na ten sam typ i kwalifikację cv co drugi. Albo jeden lub oba operandy muszą być wyrażeniem rzutu. W wersjach wcześniejszych niż program Visual Studio 2017 w wersji 15.5 kompilator zezwolił na konwersje, które są uważane za niejednoznaczne przez standard.

Po określeniu /Zc:ternary opcji kompilator jest zgodny ze standardem. Odrzuca kod, który nie spełnia reguł pasujących typów i kwalifikacji cv drugiego i trzeciego operandów.

Opcja jest /Zc:ternary domyślnie wyłączona w programie Visual Studio 2017. Umożliwia /Zc:ternary włączenie zachowania zgodnego lub /Zc:ternary- jawne określenie poprzedniego niezgodnego zachowania kompilatora. Opcja /permissive- niejawnie włącza tę opcję, ale można ją zastąpić za pomocą polecenia /Zc:ternary-.

Przykłady

W tym przykładzie pokazano, jak klasa, która zapewnia zarówno inicjację niejednoznaczną z typu, jak i konwersję na typ, może prowadzić do niejednoznacznych konwersji. Ten kod jest domyślnie akceptowany przez kompilator, ale odrzucany, gdy /Zc:ternary jest określony./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;
}

Aby naprawić ten kod, należy jawnie rzutować go do preferowanego typu wspólnego lub uniemożliwić konwersję typu w jednym kierunku. Kompilator może zachować dopasowanie konwersji typu, tworząc konwersję jawną.

Ważnym wyjątkiem od tego wspólnego wzorca jest to, że typ operandów jest jednym z typów ciągów zakończonych wartością null, takich jak const char*, const char16_t*i tak dalej. Można również odtworzyć efekt za pomocą typów tablic i typów wskaźników, do których się rozpadają. Zachowanie, gdy rzeczywisty drugi lub trzeci operand ?: jest literałem ciągu odpowiadającego typu, zależy od używanego standardu językowego. Język C++17 zmienił semantykę dla tego przypadku z języka C++14. W związku z tym kompilator akceptuje kod w poniższym przykładzie w obszarze domyślnym /std:c++14, ale odrzuca go podczas określania lub późniejszego /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
}

Aby naprawić ten kod, jawnie rzutuj jeden z operandów.

W obszarze /Zc:ternarykompilator odrzuca operatory warunkowe, w których jeden z argumentów ma typ void, a drugi nie jest wyrażeniem throw . Typowym zastosowaniem tego wzorca są makra podobne do ASER:

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

Typowym rozwiązaniem jest zastąpienie argumentu niepustego argumentem void().

W tym przykładzie pokazano kod, który generuje błąd w obszarze i /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
}

Ten kod wcześniej dał ten błąd:

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

W przypadku /Zc:ternarymetody przyczyna awarii staje się jaśniejsza. Każda z kilku konwencji wywoływania zdefiniowanych przez implementację może służyć do generowania poszczególnych lambda. Jednak kompilator nie ma reguły preferencji, aby uściślić możliwe podpisy lambda. Nowe dane wyjściowe wyglądają następująco:

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

Typowe źródło problemów znalezionych przez /Zc:ternary program pochodzi z operatorów warunkowych używanych w metaprogramowania szablonu. Niektóre typy wyników zmieniają się pod tym przełącznikiem. W poniższym przykładzie pokazano dwa przypadki, w których /Zc:ternary zmienia typ wyniku wyrażenia warunkowego w kontekście programowania bez metaprogramowania:

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

Typową poprawką std::remove_reference jest zastosowanie cech w typie wyniku, gdzie jest to konieczne, aby zachować stare zachowanie.

Aby uzyskać więcej informacji na temat problemów ze zgodnością w programie Visual C++, zobacz Zachowanie niezgodne.

Aby ustawić tę opcję kompilatora w środowisku programowania Visual Studio

  1. Otwórz okno dialogowe Strony właściwości projektu. Aby uzyskać szczegółowe informacje, zobacz Set C++ compiler and build properties in Visual Studio (Ustawianie właściwości kompilatora języka C++ i kompilowania w programie Visual Studio).

  2. Wybierz stronę Właściwości>konfiguracji C/C++>Wiersza polecenia.

  3. Zmodyfikuj właściwość Opcje dodatkowe, aby uwzględnić /Zc:ternary lub/Zc:ternary-, a następnie wybierz przycisk OK.

Zobacz też

/Zc (Zgodność)