g++ c++17 class template argument deduction not working in a very specific case
Asked Answered
O

2

11

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

Overturn answered 23/10, 2018 at 14:0 Comment(6)
even more puzzling is that lit ('b') + (lit('d')) does compile.Edla
gcc bug, filed 87712Certie
... which you apparently filed already as 87709.Certie
@Certie Aaaaand now we know your surname :PMareld
@Lightness Not exactly something I've gone out of my way to hide.Certie
@Certie A good thing too ^_^Mareld
C
2

Edit: this bug is now fixed by https://gcc.gnu.org/g:5f1a2cb9c2dc09eed53da5d5787d14bec700b10b.


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.

Cooee answered 25/10, 2018 at 9:48 Comment(1)
Sounds good. You should probably post this analysis in the bug report gcc.gnu.org/bugzilla/show_bug.cgi?id=87709Overturn
M
6

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.

Mareld answered 23/10, 2018 at 14:11 Comment(0)
C
2

Edit: this bug is now fixed by https://gcc.gnu.org/g:5f1a2cb9c2dc09eed53da5d5787d14bec700b10b.


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.

Cooee answered 25/10, 2018 at 9:48 Comment(1)
Sounds good. You should probably post this analysis in the bug report gcc.gnu.org/bugzilla/show_bug.cgi?id=87709Overturn

© 2022 - 2024 — McMap. All rights reserved.