g++ c++17 class template argument deduction not wo

2019-06-16 05:58发布

问题:

I have the following code:

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
}

This compiles fine with clang (as I would expect), but gcc produces the following error:

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> {
       ^~~

It seems to not be able to figure out the class template deduction from the constructor only in one very specific case (r2). I am assuming gcc is wrong, but can someone explain why it would fail only in this very specific case?

Example here: https://wandbox.org/permlink/jQCOhXFFQekS17Y1

回答1:

This is a brand new feature in C++17, and therefore brand new in GCC. The pattern you've observed — or lack thereof — looks very much like a compiler bug. The way that it is triggered apparently randomly also fits that pattern.

Delving further into the exact hows and whys is a tedious job for the GCC devs, not for a Stack Overflow answer, as it's likely to be extraordinarily complex… but the correct approach now is to raise a bug and watch what happens. (OP has now done that, as bug 87709.)

Related examples do already exist on Bugzilla.



回答2:

This is what I believe has happened:

There are two kinds of expressions that look similar but have vastly different meaning:

(type) + expr
(expr) + expr

The first is a C-style cast expression, that converts the unary expression + expr to type; the second is a binary expression that performs addition.

To disambiguate an expression of form (something) + expr, GCC first assumes that something is a type and does a tentative parse. If that succeeds, then the whole expression is treated as a cast expression; otherwise, something is reparsed as an expression.

Now here's where I think the bug resides: during the tentative parse, GCC wrongly believes that class template argument deduction (CTAD) cannot appear, so it issues an error when CTAD appears. But in fact, even though the tentative parse will definitely fail in this case, something may still be a valid function-style cast expression, and thus the reparse might be successful.

For cat((lit('b')), lit('d')), lit('b') + lit('d'), and (lit('b')), GCC is clever enough to see that they can't be C-style cast expression, so it does not do the tentative parse. For (lit<char>('b')) + lit('d'), there's no CTAD in lit<char>('b'), so it is fine as well.

Prove of the above analysis:

If + is changed to / (or most operators other than -, * or &), no error occurs, because (something) / expr can't be a valid cast expression.

Similar ambiguity exists in sizeof(something) (could be sizeof(type) or sizeof(expr)), and as expected, sizeof(lit(0)) triggers a similar error.