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
