g ++ c ++ 17类模板参数推导在特定情况下不起作用

时间:2018-10-23 14:00:03

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

我有以下代码:

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

2 个答案:

答案 0 :(得分:3)

这是C ++ 17中的全新功能,因此是GCC中的全新功能。您观察到的模式(或缺乏模式)非常像编译器错误。显然是随机触发的方式也符合该模式。

深入研究确切的方式和原因对于GCC开发人员来说是一项繁琐的工作,而不是对于Stack Overflow的答案,因为这可能非常复杂……但是现在正确的方法是提出一个错误并观察发生的情况。 (OP现在已完成,为bug 87709。)

相关示例do already exist on Bugzilla

答案 1 :(得分:2)

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

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

(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,所以也很好。

以上分析的证明:

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

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