On Wed, Feb 18, 2026 at 05:50:09PM +0900, Jason Merrill wrote:
> This seems like the sort of flag that we need to avoid passing down to
> recursive overload resolution, like tf_decltype in tsubst_expr.
> 
> I'm not sure that it's actually necessary in this case since you don't check
> it in build_user_type_conversion_1, but I suspect it will get passed on to
> places that do general expression substitution.

Ok.  In finish_call_expr with many spots passing complain around I've
just filtered tf_any_viable out of complain and passed the original
one only to the single specific case (clearly I was wrong when thinking
perform_koenig_lookup could return a class object, so I've dropped the
build_op_call case too).
And in build_new_function_call I've filtered it out from resolve_args,
the rest seems to be ok.  Similarly in perform_overload_resolution
I've filtered it out from add_candidates call.

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*'"
Ok for trunk if it passes full bootstrap/regtest?

2026-02-17  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.

--- gcc/cp/cp-tree.h.jj 2026-02-17 15:56:52.068934793 +0100
+++ gcc/cp/cp-tree.h    2026-02-18 10:45:29.523947638 +0100
@@ -6114,20 +6114,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-12 12:33:55.509613467 +0100
+++ gcc/cp/call.cc      2026-02-18 11:00:04.774350242 +0100
@@ -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<tr
 
   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<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
--- gcc/cp/parser.cc.jj 2026-02-14 18:04:56.435819235 +0100
+++ gcc/cp/parser.cc    2026-02-18 10:45:29.529160286 +0100
@@ -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,
       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,
 
          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,
            }
          *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,
        {
          /* 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,
    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-17 17:41:25.828539043 +0100
+++ gcc/cp/pt.cc        2026-02-18 10:45:29.532160235 +0100
@@ -33200,11 +33200,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/cp/semantics.cc.jj      2026-02-17 15:56:52.074376388 +0100
+++ gcc/cp/semantics.cc 2026-02-18 10:47:10.683448692 +0100
@@ -3318,10 +3318,12 @@ finish_call_expr (tree fn, vec<tree, va_
   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_
            }
 
          /* 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)
--- gcc/testsuite/g++.dg/cpp26/expansion-stmt29.C.jj    2026-02-18 
10:45:29.532827819 +0100
+++ gcc/testsuite/g++.dg/cpp26/expansion-stmt29.C       2026-02-18 
10:45:29.532827819 +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-18 
10:45:29.532926096 +0100
+++ gcc/testsuite/g++.dg/cpp26/expansion-stmt30.C       2026-02-18 
10:45:29.532926096 +0100
@@ -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 {})
+    ;
+}


        Jakub

Reply via email to