/Zc:ternary
(Enforce conditional operator rules)
Enable enforcement of C++ Standard rules for the types and const or volatile (cv) qualification of the second and third operands in a conditional operator expression.
Syntax
/Zc:ternary
[-
]
Remarks
Starting in Visual Studio 2017, the compiler supports C++ standard conditional operator (?:
) behavior. It's also known as the ternary operator. The C++ Standard requires ternary operands satisfy one of three conditions: The operands must be of the same type and const
or volatile
qualification (cv-qualification), or only one operand must be unambiguously convertible to the same type and cv-qualification as the other. Or, one or both operands must be a throw expression. In versions before Visual Studio 2017 version 15.5, the compiler allowed conversions that are considered ambiguous by the standard.
When the /Zc:ternary
option is specified, the compiler conforms to the standard. It rejects code that doesn't satisfy the rules for matched types and cv-qualification of the second and third operands.
The /Zc:ternary
option is off by default in Visual Studio 2017. Use /Zc:ternary
to enable conforming behavior, or /Zc:ternary-
to explicitly specify the previous non-conforming compiler behavior. The /permissive-
option implicitly enables this option, but it can be overridden by using /Zc:ternary-
.
Examples
This sample shows how a class that provides both non-explicit initialization from a type, and conversion to a type, can lead to ambiguous conversions. This code is accepted by the compiler by default, but rejected when /Zc:ternary
or /permissive-
is specified.
// 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;
}
To fix this code, make an explicit cast to the preferred common type, or prevent one direction of type conversion. You can keep the compiler from matching a type conversion by making the conversion explicit.
An important exception to this common pattern is when the type of the operands is one of the null-terminated string types, such as const char*
, const char16_t*
, and so on. You can also reproduce the effect with array types and the pointer types they decay to. The behavior when the actual second or third operand to ?:
is a string literal of corresponding type depends on the language standard used. C++17 has changed semantics for this case from C++14. As a result, the compiler accepts the code in the following example under the default /std:c++14
, but rejects it when you specify /std:c++17
or later.
// 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
}
To fix this code, cast one of the operands explicitly.
Under /Zc:ternary
, the compiler rejects conditional operators where one of the arguments is of type void
, and the other isn't a throw
expression. A common use of this pattern is in ASSERT-like macros:
// 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
}
The typical solution is to replace the non-void argument with void()
.
This sample shows code that generates an error under both /Zc:ternary
and /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
}
This code previously gave this 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
With /Zc:ternary
, the reason for failure becomes clearer. Any of several implementation-defined calling conventions could be used to generate each lambda. However, the compiler has no preference rule to disambiguate the possible lambda signatures. The new output looks like this:
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>)'
A common source of problems found by /Zc:ternary
comes from conditional operators used in template meta-programming. Some of the result types change under this switch. The following example demonstrates two cases where /Zc:ternary
changes a conditional expression's result type in a non-meta-programming context:
// 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;
}
The typical fix is to apply a std::remove_reference
trait on the result type, where needed to preserve the old behavior.
For more information about conformance issues in Visual C++, see Nonstandard Behavior.
To set this compiler option in the Visual Studio development environment
Open the project's Property Pages dialog box. For details, see Set C++ compiler and build properties in Visual Studio.
Select the Configuration Properties > C/C++ > Command Line property page.
Modify the Additional Options property to include
/Zc:ternary
or/Zc:ternary-
and then choose OK.
See also
Feedback
https://aka.ms/ContentUserFeedback.
În curând: Pe parcursul anului 2024, vom elimina treptat Probleme legate de GitHub ca mecanism de feedback pentru conținut și îl vom înlocui cu un nou sistem de feedback. Pentru mai multe informații, consultați:Trimiteți și vizualizați feedback pentru