Bug 63198 - decltype in template function declaration yields spurious error
Summary: decltype in template function declaration yields spurious error
Status: RESOLVED FIXED
Alias: None
Product: gcc
Classification: Unclassified
Component: c++ (show other bugs)
Version: 4.9.1
: P3 normal
Target Milestone: 14.0
Assignee: Patrick Palka
URL:
Keywords: rejects-valid
Depends on:
Blocks:
 
Reported: 2014-09-07 15:36 UTC by Bob Abeles
Modified: 2023-09-22 20:00 UTC (History)
4 users (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed: 2021-08-03 00:00:00


Attachments
reproduces decltype bug (210 bytes, text/x-csrc)
2014-09-07 15:36 UTC, Bob Abeles
Details

Note You need to log in before you can comment on or make changes to this bug.
Description Bob Abeles 2014-09-07 15:36:25 UTC
Created attachment 33455 [details]
reproduces decltype bug

Attempting to use decltype to name an argument type in a template function declaration results in a spurious error message. For example,

// Reproduce decltype bug, condensed from Spirit X3
// gcc 4.9.1 fails to identify f1's declaration
template <typename T>
struct s1;
template <typename T>
struct s2 {
  typedef s1<T> t1;
};
template <typename T>
struct s1 {
  template <typename T2>
  s2<T> operator = (T2 const &) const;
};

struct T;
s1<T> v1;
s2<T> v2;
template <typename T>
void f1(decltype(v1 = v2)::t1);

Compiling this yields:

blackice:decltype-bug rabeles$ ~/Development/gcc/gcc-4.9.1-baseline-build/gcc/cc1plus test1.cpp --std=c++1y
test1.cpp:19:30: error: variable or field ‘f1’ declared void
 void f1(decltype(v1 = v2)::t1);
Comment 1 Jonathan Wakely 2014-09-08 08:55:18 UTC
struct X { using t1 = int; };
struct Y { X operator=(const Y&); } y;
template <typename T> void f1(decltype(y = y)::t1);

$ g++ -std=c++11 -c d.cc
d.cc:3:50: error: variable or field ‘f1’ declared void
 template <typename T> void f1(decltype(y = y)::t1);
                                                  ^

Using -std=c++1y is not necessary, this doesn't use any C++14 features.

The error only happens if f1 is a template, and only seems to happen if the decltype expression uses the assignment operator, not any other expression I tried.
Comment 2 Bob Abeles 2014-11-03 14:37:06 UTC
This bug occurs when cp/parser.c:cp_parser_name() while parsing 'decltype(y = y)::t1' is called on 't1'. Earlier, the decltype has been determined to be dependent (it isn't actually in this case, but doing so appears to be a reasonable simplification), so cp_parser_name() defers the look up and returns the result of cp/tree.c:build_qualified_name(). 

The calling routine, cp/parser.c:cp_parser_class_name() fails to handle this case, returning an error_mark_node.
Comment 3 Thomas Bernard 2014-11-05 22:29:10 UTC
I tried investigating this and it seems that decltype( v1 = v2 )::t1 should be handled as a dependent type, but isn't. I modified the test case like this

template <typename T>
void f1(typename decltype(v1 = v2)::t1);

and the problem seems to be solved.
Comment 4 Thomas Bernard 2014-11-06 23:25:22 UTC
I investigated a bit further and I come up to the same conclusion as Bob Abeles.

Having an assignment operator inside the decltype makes gcc believe the type is dependent. There are a couple of spots where the assignment operator is handled specifically, most probably it isn't handled correctly inside the decltype.
Comment 5 Thomas Bernard 2014-11-07 16:34:05 UTC
I think I found the root of the problem. I'm preparing a patch.
Comment 6 Thomas Bernard 2014-11-16 21:03:15 UTC
The assignment operator is an operator which always has side effects. That is why it is considered dependent during template definitions to prevent early instanciations of code. 

By adding a typename before the decltype, everything is fine. Trying to add the typename implicitly works well to solve this small test case. However, using the Spirit X3 code, it only moves the problem.

The template function overload doesn't get resolved correctly, so there must be some other solution that is more appropriate.

Figuring out what exactly is going wrong was above my capacities, so I'll leave this to the pros.
Comment 7 GCC Commits 2023-09-18 18:48:10 UTC
The master branch has been updated by Patrick Palka <ppalka@gcc.gnu.org>:

https://gcc.gnu.org/g:6e92a6a2a72d3b7a5e1b29042d8a6a43fe1085aa

commit r14-4111-g6e92a6a2a72d3b7a5e1b29042d8a6a43fe1085aa
Author: Patrick Palka <ppalka@redhat.com>
Date:   Mon Sep 18 14:47:52 2023 -0400

    c++: non-dependent assignment checking [PR63198, PR18474]
    
    This patch makes us recognize and check non-dependent simple assigments
    ahead of time, like we already do for compound assignments.  This means
    the templated representation of such assignments will now usually have
    an implicit INDIRECT_REF (due to the reference return type), which the
    -Wparentheses code needs to handle.  As a drive-by improvement, this
    patch also makes maybe_convert_cond issue -Wparentheses warnings ahead
    of time, and removes a seemingly unnecessary suppress_warning call in
    build_x_modify_expr.
    
    On the libstdc++ side, some tests were attempting to modify a data
    member from a uninstantiated const member function, which this patch
    minimally fixes by making the data member mutable.
    
            PR c++/63198
            PR c++/18474
    
    gcc/cp/ChangeLog:
    
            * semantics.cc (maybe_convert_cond): Look through implicit
            INDIRECT_REF when deciding whether to issue a -Wparentheses
            warning, and consider templated assignment expressions as well.
            (finish_parenthesized_expr): Look through implicit INDIRECT_REF
            when suppressing -Wparentheses warning.
            * typeck.cc (build_x_modify_expr): Check simple assignments
            ahead time too, not just compound assignments.  Give the second
            operand of MODOP_EXPR a non-null type so that it's not considered
            always instantiation-dependent.  Don't call suppress_warning.
    
    gcc/testsuite/ChangeLog:
    
            * g++.dg/cpp0x/static_assert15.C: Expect diagnostic for
            non-constant static_assert condition.
            * g++.dg/expr/unary2.C: Remove xfails.
            * g++.dg/template/init7.C: Make initializer type-dependent to
            preserve intent of test.
            * g++.dg/template/recurse3.C: Likewise for the erroneous
            statement.
            * g++.dg/template/non-dependent26.C: New test.
            * g++.dg/warn/Wparentheses-32.C: New test.
    
    libstdc++-v3/ChangeLog:
    
            * testsuite/26_numerics/random/discard_block_engine/cons/seed_seq2.cc:
            Make data member seed_seq::called mutable.
            * testsuite/26_numerics/random/independent_bits_engine/cons/seed_seq2.cc:
            Likewise.
            * testsuite/26_numerics/random/linear_congruential_engine/cons/seed_seq2.cc:
            Likewise.
            * testsuite/26_numerics/random/mersenne_twister_engine/cons/seed_seq2.cc:
            Likewise.
            * testsuite/26_numerics/random/shuffle_order_engine/cons/seed_seq2.cc:
            Likewise.
            * testsuite/26_numerics/random/subtract_with_carry_engine/cons/seed_seq2.cc:
            Likewise.
            * testsuite/ext/random/simd_fast_mersenne_twister_engine/cons/seed_seq2.cc:
            Likewise.
Comment 8 Patrick Palka 2023-09-18 19:13:34 UTC
Fixed for GCC 14.