Considering: #include <algorithm> #include <array> #include <iostream> #include <span> #include <utility> auto f(int const n) { using s_t = std::pair<int, int>; #if defined(WITH_CONST) const #else // gcc 14 emits incorrect code with "constexpr" constexpr #endif std::array a_vec{s_t{1, 123}}; auto const vec{[&a_vec]() -> std::span<s_t const> { return a_vec; }()}; { auto const it = std::ranges::find_if(vec, [n](auto const& v) { std::cout << v.first << ", " << v.second << '\n'; return n >= v.first; }); if (it != std::ranges::end(vec)) { return it->second; } } return -1; } int main() { auto const r = f(1); std::cout << "Found=" << r << " expected=123\n"; } Using g++ 14.2 >g++ -std=c++20 -O0 -DWITH_CONST ex.cpp >./a.out 1, 123 Found=123 expected=123 But >g++ -std=c++20 -O0 exp.cpp >./a.out -2120994312, 32766 Found=32766 expected=123 Using "constepxr std::array" instead of "const std::array" results in incorrect code. This can be reproduced with: https://godbolt.org/z/Ysoc59179
Reduced: struct span { #if 1 span(const int (&__first)[1]) : _M_ptr(__first) {} #else span(const int* __first) : _M_ptr(__first) {} #endif int operator[](long __i) { return _M_ptr[__i]; } const int *_M_ptr; }; int main() { constexpr int a_vec[]{1}; auto vec{[&a_vec]() -> span { return a_vec; }()}; if (vec[0] != 1) __builtin_abort(); } Started with r8-3497-g281e6c1d8f1b4c. If we toggle the #if then we instead ICE from build_address.
Working on this one.
Interestingly enough, it's the trailing type that's causing issues; things work perfectly fine without it. That's what I'm digging into.
The master branch has been updated by Simon Martin <simartin@gcc.gnu.org>: https://gcc.gnu.org/g:fdf846fdddcc0467b9f025757f081c5d54319d08 commit r15-7849-gfdf846fdddcc0467b9f025757f081c5d54319d08 Author: Simon Martin <simon@nasilyan.com> Date: Thu Mar 6 10:10:45 2025 +0100 c++: Don't replace INDIRECT_REFs by a const capture proxy too eagerly [PR117504] We have been miscompiling the following valid code since GCC8, and r8-3497-g281e6c1d8f1b4c === cut here === struct span { span (const int (&__first)[1]) : _M_ptr (__first) {} int operator[] (long __i) { return _M_ptr[__i]; } const int *_M_ptr; }; void foo () { constexpr int a_vec[]{1}; auto vec{[&a_vec]() -> span { return a_vec; }()}; } === cut here === The problem is that perform_implicit_conversion_flags (via mark_rvalue_use) replaces "a_vec" in the return statement by a CONSTRUCTOR representing a_vec's constant value, and then takes its address when invoking span's constructor. So we end up with an instance that points to garbage instead of a_vec's storage. As per Jason's suggestion, this patch simply removes the calls to mark_*_use from perform_implicit_conversion_flags, which fixes the PR. PR c++/117504 gcc/cp/ChangeLog: * call.cc (perform_implicit_conversion_flags): Don't call mark_{l,r}value_use. gcc/testsuite/ChangeLog: * g++.dg/cpp2a/constexpr-117504.C: New test. * g++.dg/cpp2a/constexpr-117504a.C: New test.
Fixed in GCC 15. I'll let it bake there for 2-3 weeks and then backport to active branches.
The releases/gcc-14 branch has been updated by Simon Martin <simartin@gcc.gnu.org>: https://gcc.gnu.org/g:f078a613bf85eff138c2567b599779dee6ae4b22 commit r14-11445-gf078a613bf85eff138c2567b599779dee6ae4b22 Author: Simon Martin <simon@nasilyan.com> Date: Tue Mar 25 07:08:16 2025 +0100 c++: Don't replace INDIRECT_REFs by a const capture proxy too eagerly [PR117504] We have been miscompiling the following valid code since GCC8, and r8-3497-g281e6c1d8f1b4c === cut here === struct span { span (const int (&__first)[1]) : _M_ptr (__first) {} int operator[] (long __i) { return _M_ptr[__i]; } const int *_M_ptr; }; void foo () { constexpr int a_vec[]{1}; auto vec{[&a_vec]() -> span { return a_vec; }()}; } === cut here === The problem is that perform_implicit_conversion_flags (via mark_rvalue_use) replaces "a_vec" in the return statement by a CONSTRUCTOR representing a_vec's constant value, and then takes its address when invoking span's constructor. So we end up with an instance that points to garbage instead of a_vec's storage. As per Jason's suggestion, this patch simply removes the calls to mark_*_use from perform_implicit_conversion_flags, which fixes the PR. PR c++/117504 gcc/cp/ChangeLog: * call.cc (perform_implicit_conversion_flags): Don't call mark_{l,r}value_use. gcc/testsuite/ChangeLog: * g++.dg/cpp2a/constexpr-117504.C: New test. * g++.dg/cpp2a/constexpr-117504a.C: New test. (cherry picked from commit fdf846fdddcc0467b9f025757f081c5d54319d08)
The releases/gcc-13 branch has been updated by Simon Martin <simartin@gcc.gnu.org>: https://gcc.gnu.org/g:f10853a0087bc115c8ee1ddb5fc641bffdb7f1a4 commit r13-9450-gf10853a0087bc115c8ee1ddb5fc641bffdb7f1a4 Author: Simon Martin <simon@nasilyan.com> Date: Tue Mar 25 09:26:26 2025 +0100 c++: Don't replace INDIRECT_REFs by a const capture proxy too eagerly [PR117504] We have been miscompiling the following valid code since GCC8, and r8-3497-g281e6c1d8f1b4c === cut here === struct span { span (const int (&__first)[1]) : _M_ptr (__first) {} int operator[] (long __i) { return _M_ptr[__i]; } const int *_M_ptr; }; void foo () { constexpr int a_vec[]{1}; auto vec{[&a_vec]() -> span { return a_vec; }()}; } === cut here === The problem is that perform_implicit_conversion_flags (via mark_rvalue_use) replaces "a_vec" in the return statement by a CONSTRUCTOR representing a_vec's constant value, and then takes its address when invoking span's constructor. So we end up with an instance that points to garbage instead of a_vec's storage. As per Jason's suggestion, this patch simply removes the calls to mark_*_use from perform_implicit_conversion_flags, which fixes the PR. PR c++/117504 gcc/cp/ChangeLog: * call.cc (perform_implicit_conversion_flags): Don't call mark_{l,r}value_use. gcc/testsuite/ChangeLog: * g++.dg/cpp2a/constexpr-117504.C: New test. * g++.dg/cpp2a/constexpr-117504a.C: New test. (cherry picked from commit fdf846fdddcc0467b9f025757f081c5d54319d08)
Fixed in GCC 13 and 14. I won't backport further since 12 will become inactive soon.