/Zc:twoPhase-(禁用两阶段名称查找)

/permissive- 下的 /Zc:twoPhase- 选项告知编译器使用原始的不遵从标准的 Microsoft C++ 编译器行为来分析和实例化类模板及函数模板。

语法

/Zc:twoPhase-

备注

Visual Studio 2017 版本 15.3 及更高版本:在 /permissive- 下,编译器使用两阶段名称查找来解析模板名称。 如果你还指定了 /Zc:twoPhase-,编译器将还原为其先前不遵从标准的类模板和函数模板名称解析及替换行为。 如果未指定 /permissive-,则不遵从标准的行为是默认行为。

版本 10.0.15063.0(Creators Update 或 RS2)和更低版本中的 Windows SDK 头文件在遵从性模式下无法正常工作。 使用 /permissive- 时,需要使用 /Zc:twoPhase- 来编译这些 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- 下以遵从性模式编译时,此程序会输出“Standard two-phase”,因为当编译器到达模板时,func 的第二个重载不可见。 如果添加 /Zc:twoPhase-,程序会打印“Microsoft one-phase”。 输出与未指定 /permissive- 时的输出相同。

依赖名称是依赖于模板参数的名称。 这些名称的查找行为在 /Zc:twoPhase- 下也不相同。 在遵从性模式下,依赖名称不会在模板的定义点上绑定。 编译器会在实例化模板时查找这些名称。 对于具有依赖函数名称的函数调用,该名称将绑定到模板定义中调用位置处可见的函数。 在模板定义点和模板实例化点,都加载了参数依赖的查找中的其他重载。

两阶段查找由两个部分组成:在模板定义期间查找非依赖名称,在模板实例化期间查找依赖名称。 在 /Zc:twoPhase- 下,编译器不会将参数依赖的查找与非限定查找分开执行。 即,它不执行两阶段查找,因此重载解析结果可能不同。

再提供一个示例:

// 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) 设置的重载,即使它是在函数模板之后声明的也是如此。

更新代码以遵从两阶段查找标准

早期版本的编译器在 C++ Standard 需要关键字 templatetypename 的任何位置都不需要这些关键字。 某些位置需要这些关键字来澄清编译器在第一查找阶段应如何分析依赖名称。 例如:

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

遵从标准的编译器将 Foo 分析为 T 范围内的变量,这意味着,此代码是一个逻辑或表达式,它使用 T::foo < a 作为左操作数,使用 b > (c) 作为右操作数。 如果你打算将 Foo 用作函数模板,则必须通过添加 template 关键字来指明它是一个模板:

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

在 Visual Studio 2017 版本 15.3 及更高版本中,如果指定 /permissive-/Zc:twoPhase-,则编译器允许此代码不包含 template 关键字。 它将此代码解释为使用 a || b 参数调用函数模板,因为它仅以受限的方式分析模板。 在第一阶段根本不会分析上述代码。 在第二阶段,有足够的上下文来判断 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 关键字。 在遵从性模式下,编译器现在还要求在第 4 行包含 template 关键字来指明 T::X<T> 是一个模板。 请查找缺少此关键字的代码,并提供此关键字以使代码遵从标准。

有关遵从性问题的详细信息,请参阅 Visual Studio 中的 C++ 遵从性改进非标准行为

在 Visual Studio 开发环境中设置此编译器选项

  1. 打开项目的“属性页” 对话框。 有关详细信息,请参阅在 Visual Studio 中设置 C++ 编译器和生成属性

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

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

另请参阅

/Zc(一致性)