使用/ permissive-在Visual C++编译时出现奇怪的模板错误

9

我将尝试使用/permissive-在VS2019中编译一些涉及模板和重载的代码,但出现了一些奇怪的问题。 (https://godbolt.org/z/fBbQu6)

正如在godbolt上所示,当我的templateFunc()在两个重载之间声明时,会发生奇怪的事情:

namespace Foospace {
    class A;
    void func(A*) {};

    template<class T> void templateFunc() { Foospace::func((T*)0); }

    class B;
    void func(B*) {};

    void func() { Foospace::templateFunc<B>(); }
}

我遇到了错误 C2664: 'void Foospace::func(Foospace::A *)': 无法将参数1从'T *'转换为'Foospace::A *'

如果我将 templateFunc() 放在重载函数后面,显然可以正常工作:

namespace Foospace {
    class A;
    void func(A*) {};

    class B;
    void func(B*) {};

    template<class T> void templateFunc() { Foospace::func((T*)0); }

    void func() { Foospace::templateFunc<B>(); }
}

如果我将templateFunc()移到这两个重载函数之前,也可以正常工作:

namespace Foospace {
    template<class T> void templateFunc() { Foospace::func((T*)0); }

    class A;
    void func(A*) {};

    class B;
    void func(B*) {};

    void func() { Foospace::templateFunc<B>(); }
}

如果我保留templateFunc()在这两个重载函数之间,但是将对func()的调用中的Foospace命名空间限定符删除,那么突然间它也可以工作:

namespace Foospace {
    class A;
    void func(A*) {};

    template<class T> void templateFunc() { func((T*)0); }

    class B;
    void func(B*) {};

    void func() { Foospace::templateFunc<B>(); }
}

这里正在发生什么?

这看起来像是编译器的一个错误。第三个案例应该失败。规则很复杂,但你可以从查找ADL和两阶段查找开始。 - Passer By
2个回答

6

这里有很多 C++ 规则在起作用。

正如 Herb Sutter 所说,"难度 9/10"。

让我们逐一考虑这些情况。

namespace Foospace {
    class A;
    void func(A*) {};

    template<class T> void templateFunc() { Foospace::func((T*)0); }

    class B;
    void func(B*) {};

    void func() { Foospace::templateFunc<B>(); }
}

在模板中,我们使用了限定查找。名称Foospace::func被查找并在模板定义时绑定。此时只知道func(A*),因此后续对func(B*)的调用会失败。编译器拒绝该代码是正确的。请注意,这个最近在MSVC中才实现
namespace Foospace {
    class A;
    void func(A*) {};

    class B;
    void func(B*) {};

    template<class T> void templateFunc() { Foospace::func((T*)0); }

    void func() { Foospace::templateFunc<B>(); }
}

相同的故事,只是查找结果在过载集func(A*)func(B*)中。因此,两个调用都成功。
namespace Foospace {
    template<class T> void templateFunc() { Foospace::func((T*)0); }

    class A;
    void func(A*) {};

    class B;
    void func(B*) {};

    void func() { Foospace::templateFunc<B>(); }
}

在这里查找什么也没有。代码应该无法编译。由于某种原因,MSVC编译了它,这表明它可能是一个错误或功能/扩展。GCC和clang会拒绝它

namespace Foospace {
    class A;
    void func(A*) {};

    template<class T> void templateFunc() { func((T*)0); }

    class B;
    void func(B*) {};

    void func() { Foospace::templateFunc<B>(); }
}

这里是未经资格审查的查找。适用ADL规则,因此在模板定义时无法确定func(T*)是否解析,所以第一阶段查找始终允许其继续。当已知两个声明时,在模板实例化点查找名称。代码编译正常。


MSVC现在在19.24中正确拒绝第三个示例,显示“error C2039:'func':不是'Foospace'的成员”。 - Angus Graham

1

MSVC 和两阶段查找存在一些问题。

在 Visual Studio 2017 版本 15.3 及更高版本中,默认情况下编译器使用两阶段名称查找来解析模板名称。

/permissive- 选项隐式设置了符合规范的两阶段查找编译器行为,但可以通过使用 /Zc:twoPhase- 来覆盖它。

如果指定了 /Zc:twoPhase- 选项,则编译器将恢复其先前的不符合规范的类模板和函数模板名称解析和替换行为。

设置此选项后,代码将编译。请参见godbolt demo.

相关错误报告:

  1. 为C++模板实现正确的两阶段查找

  2. 在C++/CLI项目中使用/permissive-会触发两阶段名称查找警告


请参阅此帖子及其回答,其中详细介绍了MSVC在这方面存在的一些问题。 - P.W
P.W:这两个错误报告都已经被标记为已修复。他们声称现在正确实现了两阶段查找。 另一篇帖子似乎在谈论MSVC在支持两阶段查找之前的行为。 - Angus Graham
@AngusGraham: 如果他们的说法是正确的,我们不应该在使用和不使用/Zc:twoPhase-选项时看到相同的编译器行为吗? - P.W

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接