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 plus
in some cases it calls build_op_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
perform_overload_resolution, build_new_function_call and build_op_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).

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

So far tested with
GXX_TESTSUITE_STDS=98,11,14,17,20,23,26 make -j32 -k check-g++ 
RUNTESTFLAGS="dg.exp='reflect/* expansion-stmt*'"

2026-02-12  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.
        (build_new_function_call): For tf_any_viable return void_node
        if any viable candidates are found rather than build_over_call.
        (build_op_call): FOr tf_any_viable return void_node if any
        viable candidates are found instead of calling tourney etc.
        * 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.

        * g++.dg/cpp26/expansion-stmt29.C: New test.
        * g++.dg/cpp26/expansion-stmt30.C: New test.

--- gcc/cp/cp-tree.h.jj 2026-02-12 10:32:24.644331171 +0100
+++ gcc/cp/cp-tree.h    2026-02-12 10:37:18.954376200 +0100
@@ -6107,20 +6107,22 @@ 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.  */
   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
 };
--- gcc/cp/call.cc.jj   2026-02-06 11:18:47.075640238 +0100
+++ gcc/cp/call.cc      2026-02-12 11:30:08.203023552 +0100
@@ -5190,7 +5190,12 @@ perform_overload_resolution (tree fn,
 
   *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;
 
@@ -5305,10 +5310,10 @@ build_new_function_call (tree fn, vec<tr
        }
       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
@@ -5593,6 +5598,8 @@ build_op_call (tree obj, vec<tree, va_gc
     }
   else
     {
+      if (complain & tf_any_viable)
+       return void_node;
       cand = tourney (candidates, complain);
       if (cand == 0)
        {
--- gcc/cp/parser.cc.jj 2026-02-10 15:29:56.686966880 +0100
+++ gcc/cp/parser.cc    2026-02-12 11:11:34.941766559 +0100
@@ -2642,7 +2642,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
@@ -16245,8 +16245,8 @@ cp_perform_range_for_lookup (tree range,
       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
        {
@@ -16255,14 +16255,12 @@ cp_perform_range_for_lookup (tree range,
 
          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;
@@ -16270,6 +16268,29 @@ cp_perform_range_for_lookup (tree range,
            }
          *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.  */
@@ -16277,6 +16298,13 @@ cp_perform_range_for_lookup (tree range,
        {
          /* 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)
@@ -16314,20 +16342,20 @@ cp_perform_range_for_lookup (tree range,
    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;
 }
 
--- gcc/cp/pt.cc.jj     2026-02-11 11:21:46.434567063 +0100
+++ gcc/cp/pt.cc        2026-02-12 11:56:20.415553822 +0100
@@ -33197,11 +33197,9 @@ finish_expansion_stmt (tree expansion_st
       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)
     {
--- gcc/testsuite/g++.dg/cpp26/expansion-stmt29.C.jj    2026-02-12 
11:13:28.588853229 +0100
+++ gcc/testsuite/g++.dg/cpp26/expansion-stmt29.C       2026-02-12 
11:13:28.588853229 +0100
@@ -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 ();
+}
--- gcc/testsuite/g++.dg/cpp26/expansion-stmt30.C.jj    2026-02-12 
11:19:40.674588891 +0100
+++ gcc/testsuite/g++.dg/cpp26/expansion-stmt30.C       2026-02-12 
11:41:31.460519574 +0100
@@ -0,0 +1,38 @@
+// CWG 3123
+// { dg-do run { 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 {})
+    ;
+}


        Jakub

Reply via email to