【问题标题】:Is this really a dependent template name?这真的是一个依赖模板名称吗?
【发布时间】:2020-03-18 08:26:01
【问题描述】:

我有一些代码无法编译,我将其缩减为以下最低版本:

class Builder
{
    public:
        Builder()
        {}

        auto foo(int) -> Builder &
        {
            return *this;
        }

        template<typename T>
        auto bar() -> Builder &
        {
            return *this;
        }
};

template<typename T>
Builder get_builder()
{
    return Builder().foo(T()).bar<T>();
}

int main()
{
    auto builder = get_builder<int>();
    (void) builder;
}

See on Wandbox

Clang (9.0.0) 拒绝这样做:

prog.cc:22:31: error: use 'template' keyword to treat 'bar' as a dependent template name
    return Builder().foo(T()).bar<T>();
                              ^
                              template 

Clang 是否正确地说 bar 是一个依赖模板名称? VS 2017 没有问题。 GCC (9.2.0) 也拒绝该代码,但带有更模糊的错误消息:

prog.cc: In function 'Builder get_builder()':
prog.cc:22:36: error: expected primary-expression before '>' token
   22 |     return Builder().foo(T()).bar<T>();
      |                                    ^
prog.cc:22:38: error: expected primary-expression before ')' token
   22 |     return Builder().foo(T()).bar<T>();
      |       

按照 Clang 的建议更改违规行

return Builder().foo(T()).template bar<T>();

修复了 Clang 和 GCC 的编译。 VS2017 也接受这个版本。

似乎解决方案很简单,但如果我重新排序函数调用:

return Builder().bar<T>().foo(T());

或从foo中删除参数:

return Builder().foo().bar<T>();

错误消失了。

这是怎么回事?

  1. Clang 和 GCC 是否正确拒绝原始版本?
  2. Clang 和 GCC 是否正确接受更改的版本(重新排序,更改 foo)? 如果是这样,为什么?有什么区别?

【问题讨论】:

  • 我的猜测是,由于您将T 传递给foo,编译器不会尝试找出要调用的foo 的哪个重载,而是将其视为从属名称.在这种情况下,只有一个重载,因此从人类的角度来看,这似乎很明显,但很容易想象一个场景有多个 foo 重载和不同的返回类型。
  • 在这种情况下,为什么重新排序函数调用可以解决问题?
  • 因为Builder() 不依赖。你使用template bar 不是因为bar 是依赖的,而是因为foo 是依赖的。
  • 或者更准确地说是foo的返回值被认为是依赖的。
  • MSVC允许this你不觉得奇怪吗?

标签: templates gcc clang c++17 language-lawyer


【解决方案1】:

根据我的经验,MSVC(错误地)允许需要 template 关键字的代码(即使定义了 /permissive-)。

有问题的代码确实需要template 来消除bar 是模板名称的歧义。 gcc 和 clang 在诊断时是正确的。可能存在foo 的重载,它根据T 返回不同的类型。因此对bar 的调用取决于模板参数。

考虑以下几点:

struct FooBar
{
   template<class T>
   auto bar() -> T
   {
       return 42;
   }
};

class Builder
{
    public:
        Builder()
        {}

        auto foo(int) -> Builder &
        {
            return *this;
        }

        auto foo(short) -> FooBar
        {
            return {};
        }

        template<typename T>
        auto bar() -> Builder &
        {
            return *this;
        }
};

调用:

auto builder = get_builder<short>();

重载的可能性(不是实际存在)使名称依赖。参考[temp.dep]强调我的):

在模板中,一些构造具有可能因实例而异的语义。这样的构造取决于模板参数。

编译器没有义务检查模板的所有实例以查看重载集中是否只有一个可能的函数。

【讨论】:

  • 在您的示例中,有 is 重载 foo 会返回其他内容。在我的示例中,foo 没有 重载。那么你怎么能说可能存在foo 的重载?是否只是因为foo 带了一个参数,它可能 可能会被重载,所以bar 依赖?
  • 过载的可能性使其依赖,而不是实际存在
猜你喜欢
  • 1970-01-01
  • 2018-03-13
  • 2018-09-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-07-14
  • 1970-01-01
相关资源
最近更新 更多