Bug 120997 - [15 Regression] std::span<const bool, Extent>::subspan returns initializer list
Summary: [15 Regression] std::span<const bool, Extent>::subspan returns initializer list
Status: RESOLVED FIXED
Alias: None
Product: gcc
Classification: Unclassified
Component: libstdc++ (show other bugs)
Version: 15.1.1
: P3 normal
Target Milestone: 15.2
Assignee: Jonathan Wakely
URL:
Keywords: wrong-code
Depends on:
Blocks:
 
Reported: 2025-07-08 12:00 UTC by Yuhan Liu
Modified: 2025-07-11 10:05 UTC (History)
2 users (show)

See Also:
Host:
Target:
Build:
Known to work: 14.3.0
Known to fail: 15.1.0, 16.0
Last reconfirmed: 2025-07-08 00:00:00


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Yuhan Liu 2025-07-08 12:00:02 UTC
```
#include <span>
#include <iostream>
#include <array>

auto main() -> int {
  std::array<bool, 5> data{};

  std::span<const bool> data_view(data.data(), 5);
  std::cout << "data_view.data():\t" << data_view.data() << '\n';
  std::cout << "data_view.size():\t" << data_view.size() << '\n';
  std::cout << "========================================\n";

  std::span<const bool> data_view_2 = data_view.subspan(0, 5);
  std::cout << "data_view_2.data():\t" << data_view_2.data() << '\n';
  std::cout << "data_view_2.size():\t" << data_view_2.size() << '\n';
  std::cout << "========================================\n";
  return 0;
}
```
(Godbolt: https://godbolt.org/z/1Ta4rrsb5 )
The above code when compiled with "-std=c++26 -Wall -Wextra" prints
```
data_view.data():	0x7ffc7ca19b9b
data_view.size():	5
========================================
data_view_2.data():	0x7ffc7ca19b3e
data_view_2.size():	2
========================================
```

Namely, `data_view_2` is a size 2 span even though it was created with `data_view.subspan(0, 5)`. It seems like the return object in `subspan` (C++26) is being falsely interpreted as an initializer list rather than a pointer and size, causing `data_view_2` to point to the initializer list on stack (which is immediately invalidated upon return).

This bug does not occur on C++23 (-std=c++23). It also seems to only occur for `const bool` (not `bool`, not `int`, etc.).
Comment 1 Jonathan Wakely 2025-07-08 12:55:16 UTC
This fixes it:

--- a/libstdc++-v3/include/std/span
+++ b/libstdc++-v3/include/std/span
@@ -454,7 +454,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
            __glibcxx_assert(__count <= size());
            __glibcxx_assert(__offset + __count <= size());
          }
-       return {this->data() + __offset, __count};
+       return span<element_type>(this->data() + __offset, __count);
       }
 
     private:

But we need the same fix in span::first and span::last, and maybe elsewhere.


That's needed because of this new constructor in C++26 mode:

#if __cpp_lib_span_initializer_list >= 202311L // >= C++26
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Winit-list-lifetime"
      constexpr
      explicit(extent != dynamic_extent)
      span(initializer_list<value_type> __il)
      requires (is_const_v<_Type>)
      : _M_ptr(__il.begin()), _M_extent(__il.size())
      { }
#pragma GCC diagnostic pop
#endif


I haven't figured out yet whether G++ is correct to try to use that constructor here.
Comment 2 Jonathan Wakely 2025-07-08 12:57:01 UTC
The libstdc++ implementation follows the C++26 draft standard exactly:

  Effects: Equivalent to: return {data(), count};

So maybe we need to fix that.
Comment 3 Jonathan Wakely 2025-07-08 13:09:20 UTC
Yeah we have a defect in the C++26 draft, which needs to be fixed.
Comment 4 Jonathan Wakely 2025-07-08 15:15:29 UTC
Thanks for submitting the issue! I'll add the link here once it gets created.
Comment 5 GCC Commits 2025-07-09 16:56:42 UTC
The master branch has been updated by Jonathan Wakely <redi@gcc.gnu.org>:

https://gcc.gnu.org/g:a72d0e1a8bf0770ddf1d8d0ebe589f92a4fab4ef

commit r16-2152-ga72d0e1a8bf0770ddf1d8d0ebe589f92a4fab4ef
Author: Jonathan Wakely <jwakely@redhat.com>
Date:   Tue Jul 8 14:56:39 2025 +0100

    libstdc++: Do not use list-initialization in std::span members [PR120997]
    
    As the bug report shows, for span<const bool> the return statements of
    the form `return {data(), count};` will use the new C++26 constructor,
    span(initializer_list<element_type>).
    
    Although the conversions from data() to bool and count to bool are
    narrowing and should be ill-formed, in system headers the narrowing
    diagnostics are suppressed. In any case, even if the compiler diagnosed
    them as ill-formed, we still don't want the initializer_list constructor
    to be used. We want to use the span(element_type*, size_t) constructor
    instead.
    
    Replace the braced-init-list uses with S(data(), count) where S is the
    correct return type. We need to make similar changes in the C++26
    working draft, which will be taken care of via an LWG issue.
    
    libstdc++-v3/ChangeLog:
    
            PR libstdc++/120997
            * include/std/span (span::first, span::last, span::subspan): Do
            not use braced-init-list for return statements.
            * testsuite/23_containers/span/120997.cc: New test.
Comment 6 Jonathan Wakely 2025-07-11 09:52:32 UTC
(In reply to Jonathan Wakely from comment #4)
> Thanks for submitting the issue! I'll add the link here once it gets created.

https://cplusplus.github.io/LWG/issue4293
Comment 7 GCC Commits 2025-07-11 09:59:57 UTC
The releases/gcc-15 branch has been updated by Jonathan Wakely <redi@gcc.gnu.org>:

https://gcc.gnu.org/g:ab3781665da064985d66de0c895cc43588179cb6

commit r15-9952-gab3781665da064985d66de0c895cc43588179cb6
Author: Jonathan Wakely <jwakely@redhat.com>
Date:   Tue Jul 8 14:56:39 2025 +0100

    libstdc++: Do not use list-initialization in std::span members [PR120997]
    
    As the bug report shows, for span<const bool> the return statements of
    the form `return {data(), count};` will use the new C++26 constructor,
    span(initializer_list<element_type>).
    
    Although the conversions from data() to bool and count to bool are
    narrowing and should be ill-formed, in system headers the narrowing
    diagnostics are suppressed. In any case, even if the compiler diagnosed
    them as ill-formed, we still don't want the initializer_list constructor
    to be used. We want to use the span(element_type*, size_t) constructor
    instead.
    
    Replace the braced-init-list uses with S(data(), count) where S is the
    correct return type. We need to make similar changes in the C++26
    working draft, which will be taken care of via an LWG issue.
    
    libstdc++-v3/ChangeLog:
    
            PR libstdc++/120997
            * include/std/span (span::first, span::last, span::subspan): Do
            not use braced-init-list for return statements.
            * testsuite/23_containers/span/120997.cc: New test.
    
    (cherry picked from commit a72d0e1a8bf0770ddf1d8d0ebe589f92a4fab4ef)
Comment 8 Jonathan Wakely 2025-07-11 10:05:00 UTC
Fixed for GCC 15.2