On 2/18/26 7:18 PM, Jakub Jelinek wrote:
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).

Let's include in the tf_any_viable comment that it isn't currently supported for all forms of overload resolution. OK with that tweak.

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