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