https://gcc.gnu.org/g:3cd83779574847957bf341f02b601a71d672bb0b

commit r16-7581-g3cd83779574847957bf341f02b601a71d672bb0b
Author: Jakub Jelinek <[email protected]>
Date:   Thu Feb 19 07:51:50 2026 +0100

    ++: Implement proposed CWG 3123 resolution
    
    On Wed, Feb 11, 2026 at 02:56:36AM +0200, Ville Voutilainen wrote:
    > > Anyway, with the hope that CWG 3123 is voted in, this patch adds
    > > a testcase for it.
    >
    > This was discussed some moments ago on the Core reflector, and it was
    > pointed out that it's quite
    > intentional in the proposed resolution of CWG 3123 that there's no
    > overload resolution used for
    > determining whether iteration is the chosen approach instead of
    > destructuring, just a viability check.
    > As far as I understand the code, finish_call_expr indeed does overload
    > resolution.
    >
    > This additional testcase was provided: https://godbolt.org/z/o39hEs876
    >
    > In it, overload resolution fails due to an ambiguity, for iteration,
    > and then destructuring is used instead. But the intent
    > of the proposed resolution is that the semantics are (more) similar to
    > what range-for uses, so the intent
    > is that for iteration, only the presence of viable overloads is
    > checked, and then iteration is committed to,
    > and then the testcase should be rejected because the actual attempt to
    > expand with iteration fails due to the aforementioned
    > ambiguity.
    >
    > None of that affects this patch as such, but I think it's worth
    > pointing out where this should eventually be going.
    
    Ah, you're right.
    
    We don't have any API to return whether there are any viable candidates.
    I was looking into adding one, but realized that finish_call_expr
    does way too many things before calling build_new_function_call
    which does further stuff that
    would also need to be duplicated before we can reach to splice_viable.
    
    So, instead of duplicating all of that I've added tf_any_viable (using
    an unused bit in tsubst_flags_t), which is then handled in
    finish_call_expr, perform_overload_resolution and build_new_function_call
    functions to return void_node instead of actually building any calls
    if there are any viable candidates (and keep returning error_mark_node
    if something earlier fails or there are no viable candidates)
    and filtered out of complain flags for calls where it shouldn't be 
propagated.
    
    The new testcase adds some extra new tests, e.g. struct with begin and
    end non-static data members (my reading is that in that case E.begin
    and E.end has been found, so we commit to iterating expansion stmt and
    fail unless e.g. they whould have class type and usable operator ()).
    
    2026-02-19  Jakub Jelinek  <[email protected]>
    
            * cp-tree.h: Implement proposed CWG 3123 resolution.
            (enum tsubst_flags): Add tf_any_viable enumerator.  Indent all
            comments the same.
            * call.cc (perform_overload_resolution): For tf_any_viable
            return the first candidate instead of doing tourney.  Filter out
            tf_any_viable flag from add_candidates call.
            (build_new_function_call): For tf_any_viable return void_node
            if any viable candidates are found rather than build_over_call.
            Filter out tf_any_viable flag from resolve_args call.
            * parser.cc (cp_perform_range_for_lookup): Pass COMPLAIN to
            cp_range_for_member_function calls.  If not tf_error, for
            successful ADL if one or both of finish_call_expr calls returns
            error_mark_node, retry with tf_any_viable.  If both begin and
            end expressions have a viable candidate or when member lookup
            found both begin and end members, return NULL_TREE rather than
            error_mark_node even when both *begin and *end are error_mark_node.
            (cp_range_for_member_function): Add COMPLAIN argument, pass it
            down to finish_class_member_access_expr and finish_call_expr.
            * pt.cc (finish_expansion_stmt): Use esk_iterating if
            cp_perform_range_for_lookup returned something other than
            error_mark_node or if both begin_expr and end_expr are not
            error_mark_node.
            * semantics.cc (finish_call_expr): Filter out tf_any_viable
            flag from all uses of complain except one build_new_function_call
            call for the is_overloaded_fn case.
    
            * g++.dg/cpp26/expansion-stmt29.C: New test.
            * g++.dg/cpp26/expansion-stmt30.C: New test.

Diff:
---
 gcc/cp/call.cc                                | 17 +++++----
 gcc/cp/cp-tree.h                              | 22 +++++++-----
 gcc/cp/parser.cc                              | 50 +++++++++++++++++++++------
 gcc/cp/pt.cc                                  |  8 ++---
 gcc/cp/semantics.cc                           |  4 ++-
 gcc/testsuite/g++.dg/cpp26/expansion-stmt29.C | 15 ++++++++
 gcc/testsuite/g++.dg/cpp26/expansion-stmt30.C | 38 ++++++++++++++++++++
 7 files changed, 122 insertions(+), 32 deletions(-)

diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc
index c23a615fa1a9..31a5f17a2bc2 100644
--- a/gcc/cp/call.cc
+++ b/gcc/cp/call.cc
@@ -5186,11 +5186,16 @@ perform_overload_resolution (tree fn,
                  /*conversion_path=*/NULL_TREE,
                  /*access_path=*/NULL_TREE,
                  LOOKUP_NORMAL,
-                 candidates, complain);
+                 candidates, complain & ~tf_any_viable);
 
   *candidates = splice_viable (*candidates, false, any_viable_p);
   if (*any_viable_p)
-    cand = tourney (*candidates, complain);
+    {
+      if (complain & tf_any_viable)
+       cand = *candidates;
+      else
+       cand = tourney (*candidates, complain);
+    }
   else
     cand = NULL;
 
@@ -5272,7 +5277,7 @@ build_new_function_call (tree fn, vec<tree, va_gc> **args,
 
   if (args != NULL && *args != NULL)
     {
-      *args = resolve_args (*args, complain);
+      *args = resolve_args (*args, complain & ~tf_any_viable);
       if (*args == NULL)
        return error_mark_node;
     }
@@ -5305,10 +5310,10 @@ build_new_function_call (tree fn, vec<tree, va_gc> 
**args,
        }
       result = error_mark_node;
     }
+  else if (complain & tf_any_viable)
+    return void_node;
   else
-    {
-      result = build_over_call (cand, LOOKUP_NORMAL, complain);
-    }
+    result = build_over_call (cand, LOOKUP_NORMAL, complain);
 
   if (flag_coroutines
       && result
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index cc34663b9c60..2384affd7764 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -6114,20 +6114,24 @@ enum tsubst_flags {
                                    for calls in decltype (5.2.2/11).  */
   tf_partial = 1 << 8,          /* Doing initial explicit argument
                                    substitution in fn_type_unification.  */
-  tf_fndecl_type = 1 << 9,   /* Substituting the type of a function
-                               declaration.  */
-  tf_no_cleanup = 1 << 10,   /* Do not build a cleanup
-                               (build_target_expr and friends) */
-  /* 1 << 11 is available.  */
+  tf_fndecl_type = 1 << 9,      /* Substituting the type of a function
+                                   declaration.  */
+  tf_no_cleanup = 1 << 10,      /* Do not build a cleanup
+                                   (build_target_expr and friends) */
+  tf_any_viable = 1 << 11,      /* Return void_node if there are any viable
+                                   candidates.  Currently only supported on
+                                   finish_call_expr on perform_koenig_lookup
+                                   result.  */
   tf_tst_ok = 1 << 12,          /* Allow a typename-specifier to name
                                    a template (C++17 or later).  */
-  tf_dguide = 1 << 13,         /* Building a deduction guide from a ctor.  */
+  tf_dguide = 1 << 13,          /* Building a deduction guide from a ctor.  */
   tf_qualifying_scope = 1 << 14, /* Substituting the LHS of the :: operator.
                                    Affects TYPENAME_TYPE resolution from
                                    make_typename_type.  */
-  tf_no_name_lookup = 1 << 15, /* Don't look up the terminal name of an
-                                 outermost id-expression, or resolve its
-                                 constituent template-ids or qualified-ids.  */
+  tf_no_name_lookup = 1 << 15,  /* Don't look up the terminal name of an
+                                   outermost id-expression, or resolve its
+                                   constituent template-ids or
+                                   qualified-ids.  */
   /* Convenient substitution flags combinations.  */
   tf_warning_or_error = tf_warning | tf_error
 };
diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc
index a40e913893ad..13b9b8f46b47 100644
--- a/gcc/cp/parser.cc
+++ b/gcc/cp/parser.cc
@@ -2644,7 +2644,7 @@ static tree cp_parser_range_for
 static void do_range_for_auto_deduction
   (tree, tree, cp_decomp *, bool);
 static tree cp_range_for_member_function
-  (tree, tree);
+  (tree, tree, tsubst_flags_t);
 static tree cp_parser_expansion_statement
   (cp_parser *, bool *);
 static tree cp_parser_jump_statement
@@ -16229,8 +16229,8 @@ cp_perform_range_for_lookup (tree range, tree *begin, 
tree *end,
       if (member_begin != NULL_TREE && member_end != NULL_TREE)
        {
          /* Use the member functions.  */
-         *begin = cp_range_for_member_function (range, id_begin);
-         *end = cp_range_for_member_function (range, id_end);
+         *begin = cp_range_for_member_function (range, id_begin, complain);
+         *end = cp_range_for_member_function (range, id_end, complain);
        }
       else
        {
@@ -16239,14 +16239,12 @@ cp_perform_range_for_lookup (tree range, tree *begin, 
tree *end,
 
          vec_safe_push (vec, range);
 
-         member_begin = perform_koenig_lookup (id_begin, vec,
-                                               complain);
+         member_begin = perform_koenig_lookup (id_begin, vec, complain);
          if ((complain & tf_error) == 0 && member_begin == id_begin)
            return error_mark_node;
          *begin = finish_call_expr (member_begin, &vec, false, true,
                                     complain);
-         member_end = perform_koenig_lookup (id_end, vec,
-                                             tf_warning_or_error);
+         member_end = perform_koenig_lookup (id_end, vec, complain);
          if ((complain & tf_error) == 0 && member_end == id_end)
            {
              *begin = error_mark_node;
@@ -16254,6 +16252,29 @@ cp_perform_range_for_lookup (tree range, tree *begin, 
tree *end,
            }
          *end = finish_call_expr (member_end, &vec, false, true,
                                   complain);
+         if ((complain & tf_error) == 0
+             && (*begin == error_mark_node || *end == error_mark_node))
+           {
+             /* Expansion stmt should be iterating if there are any
+                viable candidates for begin and end.  If both finish_call_expr
+                with tf_none succeeded, there certainly are, if not,
+                retry with tf_any_viable to check if there were any viable
+                candidates.  */
+             if (*begin == error_mark_node
+                 && finish_call_expr (member_begin, &vec, false, true,
+                                      tf_any_viable) == error_mark_node)
+               {
+                 *end = error_mark_node;
+                 return error_mark_node;
+               }
+             if (*end == error_mark_node
+                 && finish_call_expr (member_end, &vec, false, true,
+                                      tf_any_viable) == error_mark_node)
+               {
+                 *begin = error_mark_node;
+                 return error_mark_node;
+               }
+           }
        }
 
       /* Last common checks.  */
@@ -16261,6 +16282,13 @@ cp_perform_range_for_lookup (tree range, tree *begin, 
tree *end,
        {
          /* If one of the expressions is an error do no more checks.  */
          *begin = *end = error_mark_node;
+         /* But signal to finish_expansion_stmt whether this is
+            destructuring (error_mark_node returned) or iterating
+            (something else returned).  If we got here, range.begin and
+            range.end members were found or begin (range) and end (range)
+            found any viable candidates.  */
+         if ((complain & tf_error) == 0)
+           return NULL_TREE;
          return error_mark_node;
        }
       else if (type_dependent_expression_p (*begin)
@@ -16298,20 +16326,20 @@ cp_perform_range_for_lookup (tree range, tree *begin, 
tree *end,
    Builds a tree for RANGE.IDENTIFIER().  */
 
 static tree
-cp_range_for_member_function (tree range, tree identifier)
+cp_range_for_member_function (tree range, tree identifier,
+                             tsubst_flags_t complain)
 {
   tree member, res;
 
   member = finish_class_member_access_expr (range, identifier,
-                                           false, tf_warning_or_error);
+                                           false, complain);
   if (member == error_mark_node)
     return error_mark_node;
 
   releasing_vec vec;
   res = finish_call_expr (member, &vec,
                          /*disallow_virtual=*/false,
-                         /*koenig_p=*/false,
-                         tf_warning_or_error);
+                         /*koenig_p=*/false, complain);
   return res;
 }
 
diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index 5db46cd707f1..00804a7eea15 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -33200,11 +33200,9 @@ finish_expansion_stmt (tree expansion_stmt, tree args,
       range_temp = convert_from_reference (build_range_temp (expansion_init));
       iter_type = cp_perform_range_for_lookup (range_temp, &begin_expr,
                                               &end_expr, tf_none);
-      if (begin_expr != error_mark_node && end_expr != error_mark_node)
-       {
-         kind = esk_iterating;
-         gcc_assert (iter_type);
-       }
+      if (iter_type != error_mark_node
+         || (begin_expr != error_mark_node && (end_expr != error_mark_node)))
+       kind = esk_iterating;
     }
   if (kind == esk_iterating)
     {
diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc
index 81f54f56ddaa..fbf7c6d75965 100644
--- a/gcc/cp/semantics.cc
+++ b/gcc/cp/semantics.cc
@@ -3318,10 +3318,12 @@ finish_call_expr (tree fn, vec<tree, va_gc> **args, 
bool disallow_virtual,
   tree result;
   tree orig_fn;
   vec<tree, va_gc> *orig_args = *args;
+  tsubst_flags_t orig_complain = complain;
 
   if (fn == error_mark_node)
     return error_mark_node;
 
+  complain &= ~tf_any_viable;
   gcc_assert (!TYPE_P (fn));
 
   /* If FN may be a FUNCTION_DECL obfuscated by force_paren_expr, undo
@@ -3539,7 +3541,7 @@ finish_call_expr (tree fn, vec<tree, va_gc> **args, bool 
disallow_virtual,
            }
 
          /* A call to a namespace-scope function.  */
-         result = build_new_function_call (fn, args, complain);
+         result = build_new_function_call (fn, args, orig_complain);
        }
     }
   else if (TREE_CODE (fn) == PSEUDO_DTOR_EXPR)
diff --git a/gcc/testsuite/g++.dg/cpp26/expansion-stmt29.C 
b/gcc/testsuite/g++.dg/cpp26/expansion-stmt29.C
new file mode 100644
index 000000000000..51b28dea61e6
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp26/expansion-stmt29.C
@@ -0,0 +1,15 @@
+// CWG 3123
+// { dg-do run { target c++26 } }
+
+#include <tuple>
+
+int
+main ()
+{
+  long l = 0;
+  std::tuple <int, long, unsigned> t = { 1, 2L, 3U };
+  template for (auto &&x : t)
+    l += x;
+  if (l != 6L)
+    __builtin_abort ();
+}
diff --git a/gcc/testsuite/g++.dg/cpp26/expansion-stmt30.C 
b/gcc/testsuite/g++.dg/cpp26/expansion-stmt30.C
new file mode 100644
index 000000000000..bc9017a13c63
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp26/expansion-stmt30.C
@@ -0,0 +1,38 @@
+// CWG 3123
+// { dg-do compile { target c++26 } }
+
+namespace M {
+  struct B {};
+
+  template <typename T>
+  auto *begin (T &t) { return &t.array[0]; }
+}
+
+namespace N {
+  struct S : M::B { int array[8]; };
+
+  template <typename T>
+  auto *begin (T &s) { return &s.array[0]; }
+
+  auto *end (const S &s) { return &s.array[8]; }
+}
+
+struct V { int begin, end; };
+
+namespace O {
+  struct B { int b; };
+  struct C { int operator () (int) { return 42; } } begin, end;
+}
+
+void
+foo ()
+{
+  for (auto i : N::S {})               // { dg-error "call of overloaded 
'begin\\\(N::S\\\&\\\)' is ambiguous" }
+    ;
+  template for (auto i : N::S {})
+    ;                                  // { dg-error "call of overloaded 
'begin\\\(const N::S\\\&\\\)' is ambiguous" }
+  template for (auto i : V {})
+    ;                                  // { dg-error "cannot be used as a 
function" }
+  template for (auto i : O::B {})
+    ;
+}

Reply via email to