/Zc:ternary(强制实施条件运算符规则)

允许为条件运算符表达式中第二和第三个操作数的类型和 const 或 volatile (cv) 限定强制实施 C++ 标准规则。

语法

]$

备注

从 Visual Studio 2017 开始,编译器支持 C++ 标准条件运算符 (?:) 行为。 也称为三元运算符。 C++ 标准要求三元操作数满足以下三个条件之一:操作数的类型和 constvolatile 限定(cv 限定)必须相同,或者只有一个操作数必须明确转换为与另一个操作数的类型和 cv 限定相同。 或者,其中一个或两个操作数必须是引发表达式。 在 Visual Studio 2017 版本 15.5 之前的版本中,编译器允许进行视为标准不明确的转换。

指定 /Zc:ternary 选项时,编译器符合标准。 它拒绝不符合第二和第三操作数匹配类型和 cv 限定规则的代码。

在 Visual Studio 2017 版中 /Zc:ternary 选项默认为关闭状态。 使用 /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++14,C++17 已更改了此情况的语义。 因此,编译器在默认值 /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 参数替换为 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,失败的原因变得更加清晰。 多个实现定义的调用约定中的任何一个都可以用来生成一个 lambda。 但是,编译器没有首选项规则来消除可能的 lambda 签名。 新输出如下所示:

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 发现的常见问题根源来自模板元编程中使用的条件运算符。 switch 表达式下的某些结果类型会更改。 以下示例演示了两个案例,其中 /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. 打开项目的“属性页” 对话框。 有关详细信息,请参阅在 Visual Studio 中设置 C++ 编译器和生成属性

  2. 选择“配置属性”>“C/C++”>“命令行”属性页

  3. 修改“附加选项”属性以包含 /Zc:ternary/Zc:ternary-,然后选择“确定”

另请参阅

/Zc(一致性)