/Zc:twoPhase- (2 フェーズの名前参照を無効にする)

/Zc:twoPhase- オプションは、/permissive-の下で、元の準拠していない Microsoft C++ コンパイラの動作を使用して、クラス テンプレートと関数テンプレートを解析およびインスタンス化するようにコンパイラに指示します。

構文

/Zc:twoPhase-

解説

Visual Studio 2017 バージョン 15.3 以降: /permissive-では、コンパイラはテンプレートの名前解決に 2 フェーズの名前検索を使用します。 /Zc:twoPhase-も指定した場合、コンパイラは以前に準拠していないクラス テンプレートと関数テンプレートの名前解決と置換の動作に戻ります。 /permissive-が指定されていない場合、準拠していない動作が既定です。

バージョン 10.0.15063.0 (Creators Update または RS2) 以前の Windows SDK ヘッダー ファイルは、準拠モードでは動作しません。 /Zc:twoPhase- は、 /permissive-を使用するときに、これらの SDK バージョンのコードをコンパイルするために必要です。 バージョン 10.0.15254.0 (Fall Creators Update または RS3) 以降のバージョンの Windows SDK は、準拠モードで正しく動作します。 /Zc:twoPhase- オプションは必要ありません。

コードで古い動作を正しくコンパイルする必要がある場合は、 /Zc:twoPhase- を使用します。 標準に準拠するようにコードを更新することを強くお勧めします。

コンパイラの動作 /Zc:twoPhase-

既定では、または Visual Studio 2017 バージョン 15.3 以降で、 /permissive-/Zc:twoPhase-の両方を指定すると、コンパイラはこの動作を使用します。

  • テンプレート宣言、クラスの先頭、および基底クラスのリストのみを解析します。 テンプレート本文は、トークン ストリームとしてキャプチャされます。 関数本体、初期化子、既定の引数、または noexcept の引数は解析されません。 クラス テンプレートは、クラス テンプレート内の宣言が正しいことを検証するために、仮の型で擬似インスタンス化されます。 次のクラス テンプレートを考えてみましょう。

    template <typename T> class Derived : public Base<T> { ... }
    

    テンプレート宣言 template <typename T>、クラスの先頭 class Derived、および基底クラス リスト public Base<T> は解析されますが、テンプレート本体はトークン ストリームとしてキャプチャされます。

  • 関数テンプレートを解析すると、コンパイラは関数シグネチャのみを解析します。 関数本体が解析されることはありません。 代わりに、トークン ストリームとしてキャプチャされます。

その結果、テンプレートの本文に構文エラーがあって、テンプレートがインスタンス化されない場合でも、コンパイラはエラーを診断しません。

この動作は、オーバーロードの解決にも影響します。 非標準動作は、インスタンス化のサイトでトークン ストリームが拡張される方法が原因で発生します。 テンプレート宣言で表示されなかったシンボルが、インスタンス化の時点で表示される場合があります。 これは、オーバーロードの解決に参加できることを意味します。 テンプレート定義に表示されなかったコードに基づいて、テンプレートの動作が変更される場合があります (標準に反する形で)。

たとえば、次のコードを検討してみましょう。

// zctwophase.cpp
// To test options, compile by using
// cl /EHsc /nologo /W4 zctwophase.cpp
// cl /EHsc /nologo /W4 /permissive- zctwophase.cpp
// cl /EHsc /nologo /W4 /permissive- /Zc:twoPhase- zctwophase.cpp

#include <cstdio>

void func(long) { std::puts("Standard two-phase") ;}

template<typename T> void g(T x)
{
    func(0);
}

void func(int) { std::puts("Microsoft one-phase"); }

int main()
{
    g(6174);
}

既定のモード、準拠モード、準拠モードを /Zc:twoPhase- コンパイラ オプションと共に使用する場合の出力を次に示します。

C:\Temp>cl /EHsc /nologo /W4 zctwophase.cpp && zctwophase
zctwophase.cpp
Microsoft one-phase

C:\Temp>cl /EHsc /nologo /W4 /permissive- zctwophase.cpp && zctwophase
zctwophase.cpp
Standard two-phase

C:\Temp>cl /EHsc /nologo /W4 /permissive- /Zc:twoPhase- zctwophase.cpp && zctwophase
zctwophase.cpp
Microsoft one-phase

/permissive-の準拠モードでコンパイルすると、コンパイラがテンプレートに到達したときにfuncの 2 番目のオーバーロードが表示されないため、このプログラムは "Standard two-phase" を出力します。 /Zc:twoPhase-を追加すると、"Microsoft one-phase" が出力されます。 出力は、 /permissive-を指定しない場合と同じです。

依存名は、テンプレート パラメーターに依存する名前です。 これらの名前の参照動作は、 /Zc:twoPhase-でも異なります。 準拠モードでは、依存名がテンプレートの定義の時点でバインドされていません。 代わりに、テンプレートをインスタンス化するときに、コンパイラによって検索されます。 依存する関数名を持つ関数呼び出しの場合、名前は、テンプレート定義の呼び出しサイトで表示される関数にバインドされます。 引数依存参照の他のオーバーロードは、テンプレート定義のポイントとテンプレートインスタンス化の時点の両方に追加されます。

2 フェーズ参照は、テンプレートが定義される時点での非依存名の参照と、テンプレートがインスタンス化される時点での依存名の参照という 2 つの部分で構成されます。 /Zc:twoPhase-では、コンパイラは非修飾参照とは別に引数依存の参照を行いません。 つまり、2 フェーズ参照が行われないため、オーバーロード解決の結果が異なる場合があります。

別の例を示します。

// zctwophase1.cpp
// To test options, compile by using
// cl /EHsc /W4 zctwophase1.cpp
// cl /EHsc /W4 /permissive- zctwophase1.cpp
// cl /EHsc /W4 /permissive- /Zc:twoPhase- zctwophase1.cpp

#include <cstdio>

void func(long) { std::puts("func(long)"); }

template <typename T> void tfunc(T t) {
    func(t);
}

void func(int) { std::puts("func(int)"); }

namespace NS {
    struct S {};
    void func(S) { std::puts("NS::func(NS::S)"); }
}

int main() {
    tfunc(1729);
    NS::S s;
    tfunc(s);
}

/permissive-なしでコンパイルすると、次のコードが出力されます。

func(int)
NS::func(NS::S)

/permissive-を使用してコンパイルしたが、/Zc:twoPhase-を使用しない場合、次のコードが出力されます。

func(long)
NS::func(NS::S)

/permissive-/Zc:twoPhase-の両方でコンパイルすると、次のコードが出力されます。

func(int)
NS::func(NS::S)

/permissive-の準拠モードでは、呼び出しtfunc(1729)void func(long)オーバーロードに解決されます。 /Zc:twoPhase-の場合と同様に、void func(int)オーバーロードには解決されません。 その理由は、テンプレートの定義の後に修飾されていない func(int) が宣言され、引数に依存する参照では見つからないためです。 ただし、 void func(S) は引数依存の参照に参加するため、関数テンプレートの後に宣言されている場合でも、呼び出し tfunc(s)のオーバーロード セットに追加されます。

2 フェーズで準拠するようにコードを更新する

以前のバージョンのコンパイラでは、C++ 標準で必要とされるすべての場所で、キーワード templatetypename は必要とされません。 これらのキーワードは、参照の最初のフェーズでコンパイラが依存名を解析する方法を明確にするために、いくつかの位置で必要になります。 次に例を示します。

T::Foo<a || b>(c);

準拠するコンパイラは、T のスコープ内で変数として Foo を解析します。つまり、このコードは、左側のオペランドとして T::foo < a、右側のオペランドとして b > (c) が指定された論理 OR 式です。 関数テンプレートとして Foo を使用する場合は、template キーワードを追加することで、それがテンプレートであることを示す必要があります。

T::template Foo<a || b>(c);

Visual Studio 2017 バージョン 15.3 以降では、 /permissive-/Zc:twoPhase- を指定すると、コンパイラは、 template キーワードなしでこのコードを許可します。 このコードは、限定された方法でテンプレートを解析するだけなので、a || b の引数を持つ関数テンプレートへの呼び出しとして解釈されます。 上記のコードは、最初のフェーズでは何も解析されません。 2 番目のフェーズでは、T::Foo が変数ではなくテンプレートであることを示すのに十分なコンテキストがあるため、コンパイラはキーワードの使用を強制しません。

この動作は、関数テンプレート本体、初期化子、既定の引数、および noexcept 引数の名前の前でキーワード typename を削除することによっても確認できます。 次に例を示します。

template<typename T>
typename T::TYPE func(typename T::TYPE*)
{
    /* typename */ T::TYPE i;
}

関数本体でキーワード typename を使用しない場合、このコードは /permissive- /Zc:twoPhase- でコンパイルされますが、 /permissive- 単独ではコンパイルされません。 typename キーワードは、TYPE が依存していることを示すために必要です。 本文は /Zc:twoPhase-で解析されないため、コンパイラにはキーワードは必要ありません。 /permissive-準拠モードでは、typename キーワードを指定しないコードでエラーが発生します。 Visual Studio 2017 バージョン 15.3 以降の準拠にコードを移行するには、欠落している場所に typename キーワードを挿入します。

同様に、次のコード サンプルについても考慮してみましょう。

template<typename T>
typename T::template X<T>::TYPE func(typename T::TYPE)
{
    typename T::/* template */ X<T>::TYPE i;
}

/permissive- /Zc:twoPhase-以前のコンパイラでは、コンパイラは 2 行目の template キーワードのみを必要とします。 準拠モードでは、T::X<T> がテンプレートであることを示すために、4 行目の template キーワードも必要になりました。 このキーワードが欠落しているコードを探し、コードが標準に準拠するようにキーワードを指定します。

準拠の問題の詳細については、Visual Studio での C++ 準拠の機能強化および非標準動作を参照してください。

Visual Studio 開発環境でこのコンパイラ オプションを設定するには

  1. プロジェクトの [プロパティ ページ] ダイアログ ボックスを開きます。 詳細については、Visual Studio での C++ コンパイラとビルド プロパティの設定に関する記事を参照してください。

  2. [構成プロパティ]>[C/C++]>[コマンド ライン] プロパティ ページを選択します。

  3. /Zc:twoPhase- が含まれるように [追加のオプション] プロパティを変更し、[OK] を選択します。

関連項目

/Zc (準拠)