https://gcc.gnu.org/g:48b8d2dd48aaec275e2bdd84042a0adde9c953d8
commit r16-7629-g48b8d2dd48aaec275e2bdd84042a0adde9c953d8 Author: Jakub Jelinek <[email protected]> Date: Sun Feb 22 22:07:45 2026 +0100 c++: Fix up CWG 3123 expansion stmt handling once again [PR124184] Barry Revzin mentioned in private mail that we reject the following testcase even when it should be accepted. The problem is that I thought finish_call_expr with tf_none would be just harmless, but it is not, it can result in errors while trying to instantiate something else (e.g. the noexcept). So, instead of doing finish_call_expr with tf_none and only if that returns error_mark_node calling it again with tf_any_viable, the following patch just calls it only with tf_any_viable immediately. 2026-02-22 Jakub Jelinek <[email protected]> PR c++/124184 * parser.cc (cp_perform_range_for_lookup): If tf_error is missing, call finish_call_expr after perform_koenig_lookup calls immediately with tf_any_viable instead of twice, once with tf_none and then with tf_any_viable. * pt.cc (finish_expansion_stmt): Remove unneeded parens. * g++.dg/cpp26/expansion-stmt31.C: New test. Diff: --- gcc/cp/parser.cc | 42 +++++++++++++-------------- gcc/cp/pt.cc | 2 +- gcc/testsuite/g++.dg/cpp26/expansion-stmt31.C | 32 ++++++++++++++++++++ 3 files changed, 53 insertions(+), 23 deletions(-) diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc index 13b9b8f46b47..7421cc68ff49 100644 --- a/gcc/cp/parser.cc +++ b/gcc/cp/parser.cc @@ -16178,7 +16178,13 @@ cp_convert_range_for (tree statement, tree range_decl, tree range_expr, /* Solves BEGIN_EXPR and END_EXPR as described in cp_convert_range_for. We need to solve both at the same time because the method used depends on the existence of members begin or end. - Returns the type deduced for the iterator expression. */ + Returns the type deduced for the iterator expression. + This function assumes that if COMPLAIN is not tf_warning_or_error, + it is tf_none and is called to find out if an expansion statement + is iterating (vs. destructruring) and behaves differently in that + case, in particular just checks if ADL looked up begin/end has + any viable candidates rather than doing full finish_call_expr + in that case. */ tree cp_perform_range_for_lookup (tree range, tree *begin, tree *end, @@ -16243,7 +16249,8 @@ cp_perform_range_for_lookup (tree range, tree *begin, tree *end, if ((complain & tf_error) == 0 && member_begin == id_begin) return error_mark_node; *begin = finish_call_expr (member_begin, &vec, false, true, - complain); + (complain & tf_error) ? complain + : tf_any_viable); member_end = perform_koenig_lookup (id_end, vec, complain); if ((complain & tf_error) == 0 && member_end == id_end) { @@ -16251,29 +16258,20 @@ cp_perform_range_for_lookup (tree range, tree *begin, tree *end, return error_mark_node; } *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) + (complain & tf_error) ? complain + : tf_any_viable); + if ((complain & tf_error) == 0) + { + if (*begin == error_mark_node || *end == error_mark_node) { - *begin = error_mark_node; + *begin = *end = error_mark_node; + /* Expansion stmt should be destructuring if no viable + candidate was found. */ return error_mark_node; } + /* Otherwise both are viable, so make sure to return + NULL_TREE and set *begin and *end to error_mark_node. */ + *begin = error_mark_node; } } diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc index 5d988ac52004..11711ee3543e 100644 --- a/gcc/cp/pt.cc +++ b/gcc/cp/pt.cc @@ -33226,7 +33226,7 @@ finish_expansion_stmt (tree expansion_stmt, tree args, iter_type = cp_perform_range_for_lookup (range_temp, &begin_expr, &end_expr, tf_none); if (iter_type != error_mark_node - || (begin_expr != error_mark_node && (end_expr != error_mark_node))) + || (begin_expr != error_mark_node && end_expr != error_mark_node)) kind = esk_iterating; } if (kind == esk_iterating) diff --git a/gcc/testsuite/g++.dg/cpp26/expansion-stmt31.C b/gcc/testsuite/g++.dg/cpp26/expansion-stmt31.C new file mode 100644 index 000000000000..6fa0fa4d23df --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp26/expansion-stmt31.C @@ -0,0 +1,32 @@ +// CWG 3123 +// { dg-do compile { target c++26 } } + +namespace N { +template <class C> +auto begin (C &c) noexcept (noexcept (c.begin ())) +-> decltype (c.begin ()); + +template <class C> +auto end (C &c) noexcept (noexcept (c.end ())) +-> decltype (c.end ()); + +struct D { }; +} + +template <class T> +struct E { +int x; + + struct F { + static_assert (!__is_same (T, N::D)); + }; + + F begin (); +}; + +int +main () +{ + template for (auto elem : E <N::D> ()) + ; +}
