【问题标题】:g++ c++17 class template argument deduction not working in a very specific caseg ++ c ++ 17类模板参数推导在非常特定的情况下不起作用
【发布时间】:2019-03-27 19:19:00
【问题描述】:

我有以下代码:

template <class T>
class lit {
public:
    lit(T l) : val(l) {}
    T val;
};

template <class T>
class cat {
public:
    cat(lit<T> const& a, lit<T> const& b) : a(a), b(b) {}
    lit<T> const& a;
    lit<T> const& b;
};

template <class T>
cat<T> operator+(lit<T> const& a, lit<T> const& b) {
    return cat(a, b);
}

int main() {
    auto r1 = cat((lit      ('b')),  lit('d')); // compiles
    auto r2 =     (lit      ('b')) + lit('d') ; // doesn't compile
    auto r3 =      lit      ('b')  + lit('d') ; // compiles
    auto r4 =     (lit      ('b'))            ; // compiles
    auto r5 =     (lit<char>('b')) + lit('d') ; // compiles
}

使用 clang 可以很好地编译(正如我所料),但 gcc 会产生以下错误:

prog.cc: In function 'int main()':
prog.cc:23:20: error: missing template arguments after 'lit'
     auto r2 =     (lit      ('b')) + lit('d') ; // doesn't compile
                    ^~~
prog.cc:2:7: note: 'template<class T> class lit' declared here
 class lit : public ExpressionBuilder<T> {
       ^~~

似乎只有在一种非常特殊的情况下(r2)才能从构造函数中找出类模板推导。我假设 gcc 是错误的,但有人可以解释为什么它只会在这种非常特殊的情况下失败吗?

此处示例:https://wandbox.org/permlink/jQCOhXFFQekS17Y1

【问题讨论】:

  • 更令人费解的是lit ('b') + (lit('d'))确实编译了。
  • gcc 错误,提交87712
  • ...您显然已经提交为87709
  • @Barry Aaaa 现在我们知道你的姓氏了:P
  • @Lightness 并不是我特意隐藏的东西。

标签: c++ templates c++17 template-argument-deduction class-template


【解决方案1】:

这是 C++17 中的全新功能,因此在 GCC 中也是全新的。您观察到的模式(或没有观察到的模式)看起来很像编译器错误。它的触发方式显然是随机的,也符合这种模式。

进一步研究确切的方法和原因对于 GCC 开发人员来说是一项乏味的工作,而不是 Stack Overflow 的答案,因为它可能非常复杂……但现在正确的方法是提出一个错误并观察会发生什么。 (OP 现在已经这样做了,就像bug 87709。)

相关示例do already exist on Bugzilla

【讨论】:

    【解决方案2】:

    编辑:此错误现已由 https://gcc.gnu.org/g:5f1a2cb9c2dc09eed53da5d5787d14bec700b10b 修复。


    这就是我认为已经发生的事情:

    有两种看起来相似但含义大不相同的表达方式:

    (type) + expr
    (expr) + expr
    

    第一个是 C 风格的强制转换表达式,它将一元表达式 + expr 转换为 type;第二个是执行加法的二进制表达式。

    为了消除(something) + expr 形式的表达式的歧义,GCC 首先假定something 是一个类型并进行试探性解析。如果成功,则整个表达式被视为强制转换表达式;否则,something 将被重新解析为表达式。

    现在我认为错误所在的地方:在试探性解析期间,GCC 错误地认为类模板参数推导 (CTAD) 不能出现,因此当 CTAD 出现时它会发出错误。但实际上,即使在这种情况下试探性解析肯定会失败,something 仍然可能是一个有效的函数式强制转换表达式,因此重解析可能会成功。

    对于cat((lit('b')), lit('d'))lit('b') + lit('d')(lit('b')),GCC 足够聪明,可以看出它们不能是C 风格的强制转换表达式,因此它不会进行试探性解析。对于(lit&lt;char&gt;('b')) + lit('d')lit&lt;char&gt;('b') 中没有 CTAD,所以也可以。

    证明上述分析:

    如果将+ 更改为/(或除-*&amp; 之外的大多数运算符),则不会发生错误,因为(something) / expr 不能是有效的强制转换表达式。

    sizeof(something)(可能是sizeof(type)sizeof(expr))中存在类似的歧义,正如预期的那样,sizeof(lit(0)) 会触发类似的错误。

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-12-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-03-14
    • 2021-04-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多