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
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.
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.