g++在非常特定的情况下无法使用C++17类模板参数推导功能

10
我有以下代码:

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')) 竟然可以编译通过。 - NathanOliver
GCC 缺陷,已提交 87712 - Barry
1
... 显然你已经将其归档为 87709 - Barry
@Barry 啊哈,现在我们知道你的姓氏了 :P - Lightness Races in Orbit
@Lightness 不是我特意隐藏的东西。 - Barry
@Barry 也是个好事情 ^_^ - Lightness Races in Orbit
2个回答

5
这是C++17中全新的功能,因此在GCC中也是全新的。你观察到的模式(或缺乏模式)非常类似于编译器错误。它被随机触发的方式也符合这种模式。深入研究其具体原因是GCC开发人员的一项繁琐工作,而不是Stack Overflow答案的范畴,因为它可能非常复杂...但现在正确的方法是报告错误并观察发生了什么。(OP现在已经这样做了,如错误编号87709所示。)有关示例已经存在于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<char>('b')) + lit('d'),在lit<char>('b')中没有CTAD,因此也是可以的。
上述分析的证明:
如果将+更改为/(或大多数其他运算符,除了-*&),则不会发生错误,因为(something) / expr不能是有效的强制类型转换表达式。
sizeof(something)中也存在类似的歧义(可能是sizeof(type)sizeof(expr)),如预期所示,sizeof(lit(0))会触发类似的错误。

好的。你应该将这个分析发表在错误报告 https://gcc.gnu.org/bugzilla/show_bug.cgi?id=87709 中。 - Baruch

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