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