/Zc:ternary (条件演算子ルールの強制)

条件演算子式内の 2 番目と 3 番目のオペランドの型と、const または volatile (cv) 修飾に対して、C++ 標準規則の適用を有効にします。

構文

$

解説

Visual Studio 2017 以降では、コンパイラで、C++ 標準の条件付き演算子 (?:) の動作がサポートされます。 これは三項演算子とも呼ばれます。 C++ 標準では、三項式のオペランドが、3 つの条件のうちのいずれかを満たしている必要があります。1 つ目は、各オペランドが同じ型で、const または volatile 修飾 (cv 修飾) であること。2 つ目は、一方のオペランドだけが他方のオペランドと明確に同じ型に変換可能で、同じ cv 修飾であること。 3 つ目は、一方または両方のオペランドが throw 式であることです。 Visual Studio 2017 バージョン 15.5 より前のバージョンでは、標準であいまいと見なされる変換がコンパイラで許可されていました。

/Zc:ternary オプションを指定した場合、コンパイラは標準に準拠します。 2 つ目と 3 つ目のオペランドの型と 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* など) のいずれかである場合です。 なお、配列型と、それらが減衰するポインター型を使用して、この効果を再現することもできます。 ?: に対する実際の 2 つ目または 3 つ目のオペランドが、対応する型の文字列リテラルである場合の動作は、使用される言語標準によって異なります。 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
}

このコードを修正するには、オペランドの 1 つを明示的にキャストします。

/Zc:ternary が指定されている場合、コンパイラは、引数の 1 つが void 型で、もう 1 つが 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 を使用すると、エラーの理由がより明確になります。 各ラムダの生成には、実装定義の呼び出し規則を使用できます。 ただし、コンパイラには、考えられるラムダ シグネチャのあいまいさを解消するための基本設定規則はありません。 新しい出力は次のようになります。

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 によって条件式の結果型が変わる 2 つのケースを示したものです。

// 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- を含め、[OK] を選択します。

関連項目

/Zc (準拠)