【问题标题】:Bizarre template errors when compiling in Visual C++ with /permissive-使用 /permissive- 在 Visual C++ 中编译时出现奇怪的模板错误
【发布时间】:2019-08-31 15:51:32
【问题描述】:

我正在尝试使用 /permissive- 在 VS2019 中编译一些代码,其中涉及模板和重载,并且正在发生奇怪的事情。 (https://godbolt.org/z/fBbQu6)

就像在上帝螺栓中一样,当我的 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>(); }
}

我收到error C2664: 'void Foospace::func(Foospace::A *)': cannot convert argument 1 from 'T *' to '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 和两阶段查找开始。
  • @T.C.等等,不,我很困惑。我记得在某个地方看到只有 ADL 在实例化时应用,限定名称不应该禁用它吗?
  • Nvm,我数不过来。

标签: c++ visual-studio templates


【解决方案1】:

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

作为 Herb Sutter put it,“难度 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 并“绑定”at the point of template definition。当时只知道func(A*),因此随后对func(B*) 的调用失败。编译器拒绝代码是正确的。请注意,这个 wasn't implemented 直到最近才在 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 reject it.

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*) 是否已解析,因此第 1 阶段查找总是让它继续进行。当两个声明都知道时,会查找名称at the point of template instantiation。代码编译正常。

【讨论】:

  • 优秀的答案。我只想提一下,您回复中的第四个代码是第三个代码的粘贴。
  • @AngusGraham 我认为与第三个示例相比,我的第四个示例更容易理解。但是好的,我用你的确切例子更新了它,最终结果没有区别。
  • MSVC 现在正确拒绝 19.24 中的第三个示例,并显示“错误 C2039: 'func': is not a member of 'Foospace'”
【解决方案2】:

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

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

/permissive- 选项隐式设置 conforming 两阶段查找编译器行为,但可以使用 /Zc:twoPhase- 覆盖它.

如果指定了选项 /Zc:twoPhase-,编译器将恢复到其之前的non-conforming类模板和函数模板名称解析和替换行为。

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

相关错误报告:

  1. Implement correct two-phase lookup for C++ templates

  2. Using /permissive- in a C++/CLI project triggers two-phase name lookup warnings

【讨论】:

  • 请参阅this post 及其答案,其中详细介绍了 MSVC 在这方面的一些问题。
  • P.W:这两个错误报告都已按修复关闭。他们声称现在正确实施了 2 阶段查找。另一篇文章似乎在讨论 MSVC 在支持 2 阶段查找之前的行为。
  • @AngusGraham:如果他们的声明是正确的,我们是否应该在使用和不使用 /Zc:twoPhase- 选项时看到相同的编译器行为?
猜你喜欢
  • 2013-04-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-02-25
  • 2016-06-05
  • 1970-01-01
  • 2018-09-22
  • 1970-01-01
相关资源
最近更新 更多