/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 const
volatile
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:ternary
kompilator 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:ternary
metody 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
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).
Wybierz stronę Właściwości>konfiguracji C/C++>Wiersza polecenia.
Zmodyfikuj właściwość Opcje dodatkowe, aby uwzględnić
/Zc:ternary
lub/Zc:ternary-
, a następnie wybierz przycisk OK.