/Zc:twoPhase- (2 フェーズの名前参照の無効化)

/permissive- の下にある /Zc:twoPhase- オプションは、元の準拠していない 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- の下のコンパイラ動作

/permissive-/Zc:twoPhase- の両方を指定した場合、既定、または Visual Studio 2017 バージョン 15.3 以降では、コンパイラは次のように動作します。

  • テンプレート宣言、クラスの先頭、および基底クラスのリストのみを解析します。 テンプレート本文は、トークン ストリームとしてキャプチャされます。 関数本体、初期化子、既定の引数、または 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- の下で準拠モードでコンパイルすると、このプログラムは "Standard two-phase" を出力します。これは、コンパイラがテンプレートに到達したときに、func の 2 番目のオーバーロードが表示されないためです。 /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 (準拠)