【问题标题】:Strange template compile errors: is this a g++ bug, a Clang bug, or...?奇怪的模板编译错误:这是 g++ 错误、Clang 错误还是...?
【发布时间】:2020-06-17 15:23:42
【问题描述】:

我在一些复杂的 C++ 模板代码中遇到了编译错误,我已将其简化如下:


struct MyOptions
{
    static const size_t maxArray = 2;
    static const uint maxIdx = 8;
};

class OtherClass
{
    uint num;
  public:
    OtherClass(uint val) : num(val)
    {
    }
    void OtherCall(const char *varName, uint arraySize)
    {
        std::cout << '#' << num << ": " << varName << '[' << arraySize << ']' << std::endl;
    }
    template <class OPTS_> inline void OtherMethod(const char *varName)
    {
        OtherCall(varName, OPTS_::maxIdx);
    }
};

template <size_t COUNT_> class ConstArray
{
    OtherClass *other[COUNT_];
  public:
    ConstArray(OtherClass *o1, OtherClass *o2) // Just sample logic, shouldn't hard-code 2 elements
    {
        other[0] = o1;
        other[1] = o2;
    }
    inline OtherClass *operator[](size_t idx) const
    {
        return other[idx];  // Array itself not changeable by caller
    }
};

template <class OPTS_> class MyClass
{
    ConstArray<OPTS_::maxArray> others1;
    ConstArray<2> others2;
  public:
    MyClass(OtherClass *o1, OtherClass *o2) : others1(o1, o2), others2(o1, o2)
    {   // Just test code to initialize the ConstArray<> members
    }
    inline void PrintInfo(uint idx, const char *varName)
    {
        OtherClass *other1Ptr = others1[idx];
        other1Ptr->OtherMethod<OPTS_>(varName);             // This works
        others1[idx]->OtherMethod<OPTS_>(varName);          // This FAILS!!
        others2[idx]->OtherMethod<OPTS_>(varName);          // This works
    }
};

int main(int argc, char *argv[])
{
    OtherClass a(9), b(42);
    MyClass<MyOptions> mine(&a, &b);
    mine.PrintInfo(1, "foo");
    return 0;
}

g++ 5.4.0 中“This FAILS!!”的错误消息上面的行是

error: expected primary-expression before ‘>’ token
         others1[idx]->OtherMethod<OPTS_>(varName);          // This FAILS!!
                                        ^

然而,显然当我使用临时的other1Ptr = others1[idx] 时,相同的逻辑编译得很好,分成了 2 个语句,这让我相信这是一个 g++ 错误。

但我使用在线编译器在 Clang 中尝试,得到了不同的(和冲突的)错误:

error: use 'template' keyword to treat 'OtherMethod' as a dependent template name
        others1[idx]->OtherMethod<OPTS_>(varName);  // This fails
                      ^
                      template 
error: use 'template' keyword to treat 'OtherMethod' as a dependent template name
        others2[idx]->OtherMethod<OPTS_>(varName);  // This works
                      ^
                      template 
2 errors generated.

所以 Clang 告诉我 others1[idx]-&gt;OtherMethods&lt;&gt;() 行实际上有什么问题,并另外告诉我在 g++ 中工作的 others2[idx]-&gt;OtherMethod&lt;&gt;() 行实际上是错误的!

果然,如果我更改 PrintInfo() 代码,它在 Clang 中编译得很好:

    inline void PrintInfo(uint idx, const char *varName)
    {
        OtherClass *other1Ptr = others1[idx];
        other1Ptr->OtherMethod<OPTS_>(varName);             // This works
//        others1[idx]->OtherMethod<OPTS_>(varName);          // This FAILS!!
        others1[idx]->template OtherMethod<OPTS_>(varName); // This works
//        others2[idx]->OtherMethod<OPTS_>(varName);          // This works ONLY IN g++!
        others2[idx]->template OtherMethod<OPTS_>(varName); // This works
    }

而且这段代码在 g++ 中也编译得很好,所以看起来这是正确的行为。

然而正如我们已经看到的,g++ 也接受了

        others2[idx]->OtherMethod<OPTS_>(varName);          // This works ONLY IN g++!

那么这是 g++ 中的错误吗?还是 Clang 对这个逻辑太严格了?解决方法是将others1[idx]-&gt;OtherMethod&lt;&gt;() 行分成两部分(带有一个临时变量)实际上是否正确,还是应该以某种方式使用“模板”关键字?

【问题讨论】:

  • 您是否尝试将maxArray 声明为constexpr 而不是const
  • 否;我可以尝试一下,并怀疑它可能会使 ConstArray&lt;OPTS_::maxArray&gt; 案例像 g++ 中的 ConstArray&lt;2&gt; 一样工作。尽管考虑到 Clang 的行为,这似乎与此时的问题无关。编辑:不,它不会改变 g++ 的行为。
  • 在某些情况下template 是可选的,请参阅stackoverflow.com/questions/610245/…
  • 显示相同差异的更简单代码:godbolt.org/z/DfRiv6
  • 我猜这是一个 Clang 错误。当前主干 Clang 同意 GCC。

标签: c++ templates compiler-errors g++ clang


【解决方案1】:

我认为 g++ 在这里是正确的(尽管 clang++ 有更好的错误消息措辞),而 clang++ 拒绝others2[idx]-&gt;OtherMethod&lt;OPTS_&gt;(varName); 声明是不正确的。尽管正如@walnut 在评论中指出的那样,clang 的最新源代码正确地接受了该声明。

在某些情况下,template 的要求是 C++17 中的[temp.names]/4

qualified-id 中用作 typename-specifier 中的名称,elaborated-type-specifier使用-声明,或class-or-decltype,出现在顶层的可选关键字template被忽略。在这些上下文中,始终假定&lt; 令牌引入了 template-argument-list。在所有其他上下文中,当命名未知特化([temp.dep.type])成员的模板特化时,成员模板名称应以关键字template 为前缀。

在所有相关情况下,成员模板名称OtherMethod 出现在使用-&gt; 标记的类成员访问表达式中。 OtherMethod 成员不是“当前实例化的成员”,因为在代码上下文中只有类型 MyClass&lt;OPTS_&gt;“是当前实例化”。所以通过[temp.res]/(6.3),名称OtherMethod 是“未知专业化的成员”,仅当对象表达式的类型是相关的。

声明 1:

others1[idx]->OtherMethod<OPTS_>(varName);

对象表达式为*(others1[idx])others1 是当前实例化的成员,依赖类型为ConstArray&lt;OPTS_::maxArray&gt;,因此others1 是类型相关的([temp.dep.expr]/(3.1)),others1[idx]*(others1[idx]) 也是类型相关的([temp.dep.expr]/1)。 template 关键字是必需的。

声明 2:

others2[idx]->OtherMethod<OPTS_>(varName);

这一次,others2 是当前实例化的成员,但具有非依赖类型 ConstArray&lt;2&gt;。表达式idx 命名了一个非类型模板参数,因此它是值相关的([type.dep.constexpr]/(2.2))但它不是类型相关的(它的类型总是uint,不管是什么)。所以*(others2[idx])不依赖于类型,template关键字在OtherMethod之前是可选的。

声明 3:

other1Ptr->OtherMethod<OPTS_>(varName);

对象表达式为*other1Ptrother1Ptr 的类型为OtherClass*,因此other1Ptr*other1Ptr 都不是类型相关的,而关键字template 是可选的。

将语句一分为二并不像看起来那样“等价”。在声明中

OtherClass *other1Ptr = others1[idx];

如上所述,初始化表达式others1[idx] 是依赖于类型的,但您已将特定的非依赖类型OtherClass* 赋予临时变量。如果您已将其声明为 auto other1Ptrauto *other1Ptr 等,则变量名称将取决于类型。使用显式类型OtherClass*,这两个语句加起来可能更类似于

static_cast<OtherClass*>(others1[idx])->OtherMethod<OPTS_>(varName);

这也是有效的。 (这也不完全等价,因为 static_cast 将允许一些隐式转换不允许的转换。)

【讨论】:

  • 哇,我不得不尝试你建议的 OtherClass *other1Ptr = others1[idx]; other1Ptr-&gt;template OtherMethod&lt;OPTS_&gt;(varName); 并且确实 g++ 确实接受了可选的模板说明符! (并不是我怀疑你,但根据我的经验,该语言对准确使用 typename 非常挑剔,并且仅在必要时才看到它们在不严格要求时忽略“澄清”template 说明符,而不是输出“错误,删除关键字”消息)。
  • (不可否认,大部分经验是在我以前的公司采用 C++11 之前形成的)
  • 这个答案让一个模糊的区域非常清晰。 (我也会给你点赞,但这个帐户太新了,无法注册。)
【解决方案2】:

请在“依赖名称的模板消歧器”部分下查看this link

编辑:原则上我同意@ascheper 的回答。但是,上面的相同链接还指出“与 typename 的情况一样,允许使用模板前缀,即使名称不依赖或使用未出现在模板范围内(自 C++11 起)。”因此,在您的具体情况下是允许的,但不是必需的。

【讨论】:

  • 在OP最后询问的特定情况下,不需要template关键字。请参阅 aschepler 的答案。如果您不同意该答案的结论,我建议您添加更具体的推理。
  • 有趣的是,在 C++11 中,typename 似乎不像我以前的经验所暗示的那样对确切用法很挑剔。如果我查看当前的开发,似乎即使在 C++14 中我也有相反的情况;在 C++11 之前我确实做过大量的模板项目,所以不可否认,我的大部分印象可能不再准确。我一直对使用 typedef 的类型时需要 typename 的情况感到困扰,这一直是我的烦恼,但现在我从最近的代码中看到,这些现在只是 typedef 命名本身的情况包含类型。
【解决方案3】:

再看错误信息:

error: use 'template' keyword to treat 'OtherMethod' as a dependent template name
        others1[idx]->OtherMethod<OPTS_>(varName);  // This fails

它告诉你这样做:

        others1[idx]->template OtherMethod<OPTS_>(varName);
        others2[idx]->template OtherMethod<OPTS_>(varName);

然后它就起作用了:)

编译器只是请求帮助区分OtherMethod的两个重载(即模板,或非模板方法)

【讨论】:

  • 是的,我已经在我的代码示例中展示了这种行为。问题是 g++ 接受的 others2[idx]-&gt;OtherMethod&lt;OPTS_&gt;(varName); 行是否实际上需要“模板”关键字(一个 g++ 错误),或者 Clang 是否不必要地需要它(一个 Clang 错误,尽管我怀疑是这种情况)。或者我认为“模板”在这里可能是可选的,它们都是正确的,但我有点怀疑......
  • 你最后一句话是假的,OtherMethod没有重载(是函数模板)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-04-08
  • 1970-01-01
  • 1970-01-01
  • 2011-08-03
  • 2021-04-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多