The purpose of this patch is to double-check LWG4272 [1]. This commit
must be applied after the patch for left-padded layouts [2].

This solves a lot of the non-uniformity w.r.t. convertibility. What's
left is related to how the padding value influences convertibility.
Which is a separate issue, but it means that we can't (maybe yet) get
rid of some complexity in padded.cc (search for twist).

[1]: https://cplusplus.github.io/LWG/lwg-active.html#4272
[2]: https://gcc.gnu.org/pipermail/libstdc++/2025-September/063378.html

--- 8< ---

LWG4272 proposes to add a condition for convertibility from
layout_stride::mapping to other mappings. New conversion requires
both that rank == 0 and that the extent types are convertible.

LWG4272 also proposes to add the same condition for conversion of
left-padded layouts, i.e. in addition to the condition on the padding
value, the extent types must be convertible.

libstdc++-v3/ChangeLog:

        * include/std/mdspan (layout_left): Apply LWG4272.
        (layout_right): Ditto.
        * testsuite/23_containers/mdspan/layouts/ctors.cc: Add
        test to check ctor uniformity at rank == 0. Update test
        for new behavior.
        * testsuite/23_containers/mdspan/layouts/padded.cc: Update test
        for new behavior.

Signed-off-by: Luc Grosheintz <luc.groshei...@gmail.com>
---
 libstdc++-v3/include/std/mdspan               | 15 +++-
 .../23_containers/mdspan/layouts/ctors.cc     | 82 +++++++++++++++----
 .../23_containers/mdspan/layouts/padded.cc    | 49 +++++++++--
 3 files changed, 119 insertions(+), 27 deletions(-)

diff --git a/libstdc++-v3/include/std/mdspan b/libstdc++-v3/include/std/mdspan
index 7d47d799f71..6d7d6315686 100644
--- a/libstdc++-v3/include/std/mdspan
+++ b/libstdc++-v3/include/std/mdspan
@@ -776,7 +776,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       // noexcept for consistency with other layouts.
       template<typename _OExtents>
        requires is_constructible_v<extents_type, _OExtents>
-       constexpr explicit(extents_type::rank() > 0)
+       constexpr explicit(!(extents_type::rank() == 0
+                            && is_convertible_v<_OExtents, extents_type>))
        mapping(const layout_stride::mapping<_OExtents>& __other) noexcept
        : mapping(__other.extents(), __mdspan::__internal_ctor{})
        { __glibcxx_assert(*this == __other); }
@@ -939,7 +940,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 
       template<typename _OExtents>
        requires is_constructible_v<extents_type, _OExtents>
-       constexpr explicit(extents_type::rank() > 0)
+       constexpr explicit(!(extents_type::rank() == 0
+                            && is_convertible_v<_OExtents, extents_type>))
        mapping(const layout_stride::mapping<_OExtents>& __other) noexcept
        : mapping(__other.extents(), __mdspan::__internal_ctor{})
        { __glibcxx_assert(*this == __other); }
@@ -1409,7 +1411,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 
        template<typename _OExtents>
          requires is_constructible_v<_OExtents, extents_type>
-         constexpr explicit(_OExtents::rank() > 0)
+         constexpr explicit(!(_OExtents::rank() == 0
+                              && is_convertible_v<_OExtents, extents_type>))
          mapping(const typename layout_stride::mapping<_OExtents>& __other)
          : _M_extents(__other.extents())
          {
@@ -1432,7 +1435,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
          requires __mdspan::__is_left_padded_mapping<_LeftPaddedMapping>
              && is_constructible_v<extents_type,
                                    typename _LeftPaddedMapping::extents_type>
-         constexpr explicit(_S_rank > 1 && (padding_value != dynamic_extent
+         constexpr explicit(
+             !is_convertible_v<typename _LeftPaddedMapping::extents_type,
+                               extents_type>
+             || _S_rank > 1
+             && (padding_value != dynamic_extent
                || _LeftPaddedMapping::padding_value == dynamic_extent))
          mapping(const _LeftPaddedMapping& __other)
          : _M_extents(__other.extents())
diff --git a/libstdc++-v3/testsuite/23_containers/mdspan/layouts/ctors.cc 
b/libstdc++-v3/testsuite/23_containers/mdspan/layouts/ctors.cc
index 290c53647cf..be07b55af03 100644
--- a/libstdc++-v3/testsuite/23_containers/mdspan/layouts/ctors.cc
+++ b/libstdc++-v3/testsuite/23_containers/mdspan/layouts/ctors.cc
@@ -248,12 +248,8 @@ namespace from_same_layout
       verify_nothrow_convertible<Layout, std::extents<unsigned int>>(
        std::extents<int>{});
 
-      if constexpr (!is_layout_leftpad<Layout>)
-       verify_nothrow_constructible<Layout, std::extents<int>>(
-         std::extents<unsigned int>{});
-      else
-       verify_convertible<Layout, std::extents<int>>(
-         std::extents<unsigned int>{});
+      verify_nothrow_constructible<Layout, std::extents<int>>(
+       std::extents<unsigned int>{});
 
       assert_not_constructible<
        typename Layout::mapping<std::extents<int>>,
@@ -263,12 +259,8 @@ namespace from_same_layout
        typename Layout::mapping<std::extents<int, 1>>,
        typename Layout::mapping<std::extents<int>>>();
 
-      if constexpr (!is_layout_leftpad<Layout>)
-       verify_nothrow_constructible<Layout, std::extents<int, 1>>(
-         std::extents<int, dyn>{1});
-      else
-       verify_convertible<Layout, std::extents<int, 1>>(
-         std::extents<int, dyn>{1});
+      verify_nothrow_constructible<Layout, std::extents<int, 1>>(
+       std::extents<int, dyn>{1});
 
       verify_nothrow_convertible<Layout, std::extents<int, dyn>>(
        std::extents<int, 1>{});
@@ -358,6 +350,67 @@ namespace from_left_or_right
     }
 }
 
+// checks: convertibility of rank == 0 mappings.
+namespace from_rank0
+{
+  template<typename SLayout, typename OLayout, typename SExtents,
+          typename OExtents>
+    constexpr void
+    verify_ctor()
+    {
+      using SMapping = typename SLayout::mapping<SExtents>;
+      using OMapping = typename OLayout::mapping<OExtents>;
+
+      constexpr bool expected = std::is_convertible_v<OExtents, SExtents>;
+      if constexpr (expected)
+       verify_nothrow_convertible<SMapping>(OMapping{});
+      else
+       verify_nothrow_constructible<SMapping>(OMapping{});
+    }
+
+  template<typename Layout, typename OLayout>
+    constexpr void
+    test_rank0_convertibility()
+    {
+      using E1 = std::extents<int>;
+      using E2 = std::extents<unsigned int>;
+
+      verify_ctor<Layout, OLayout, E1, E2>();
+      verify_ctor<Layout, OLayout, E2, E1>();
+
+      verify_ctor<Layout, OLayout, E2, E2>();
+      verify_ctor<Layout, OLayout, E1, E1>();
+    }
+
+  constexpr void
+  test_all()
+  {
+    auto run = []<typename Layout>(Layout)
+      {
+       test_rank0_convertibility<Layout, std::layout_left>();
+       test_rank0_convertibility<Layout, std::layout_right>();
+       test_rank0_convertibility<Layout, std::layout_stride>();
+      };
+
+    auto run_all = [run]()
+      {
+       run(std::layout_left{});
+       run(std::layout_right{});
+       run(std::layout_stride{});
+#if __glibcxx_submdspan
+       run(std::layout_left_padded<0>{});
+       run(std::layout_left_padded<1>{});
+       run(std::layout_left_padded<6>{});
+       run(std::layout_left_padded<dyn>{});
+#endif
+       return true;
+      };
+
+    run_all();
+    static_assert(run_all());
+  }
+}
+
 // ctor: mapping(layout_stride::mapping<OExtents>)
 namespace from_stride
 {
@@ -418,8 +471,7 @@ namespace from_stride
       verify_nothrow_convertible<Layout, std::extents<unsigned int>>(
        std::extents<int>{});
 
-      // Rank ==  0 doesn't check IndexType for convertibility.
-      verify_nothrow_convertible<Layout, std::extents<int>>(
+      verify_nothrow_constructible<Layout, std::extents<int>>(
        std::extents<unsigned int>{});
 
       verify_nothrow_constructible<Layout, std::extents<int, 3>>(
@@ -475,5 +527,7 @@ main()
 
   from_left_or_right::test_all<std::layout_left, std::layout_right>();
   from_left_or_right::test_all<std::layout_right, std::layout_left>();
+
+  from_rank0::test_all();
   return 0;
 }
diff --git a/libstdc++-v3/testsuite/23_containers/mdspan/layouts/padded.cc 
b/libstdc++-v3/testsuite/23_containers/mdspan/layouts/padded.cc
index 6e79e191ff7..3069bfe807d 100644
--- a/libstdc++-v3/testsuite/23_containers/mdspan/layouts/padded.cc
+++ b/libstdc++-v3/testsuite/23_containers/mdspan/layouts/padded.cc
@@ -90,7 +90,6 @@ is_same_mapping(const auto& lhs, const auto& rhs)
 enum class ConversionRule
 {
   Never,
-  Always,
   Conventional
 };
 
@@ -100,8 +99,6 @@ should_convert(auto rule)
 {
   if constexpr (rule == ConversionRule::Never)
     return false;
-  if constexpr (rule == ConversionRule::Always)
-    return true;
   else
     return std::is_convertible_v<typename From::extents_type,
                                 typename To::extents_type>;
@@ -192,8 +189,32 @@ test_from_left()
   return true;
 }
 
-constexpr bool
-test_from_stride()
+constexpr void
+test_from_stride_0d()
+{
+  using E1 = std::extents<uint8_t>;
+  using E2 = std::extents<uint16_t>;
+  using E3 = std::extents<size_t, 6>;
+
+  auto check = []<typename PaddedLayout>(PaddedLayout)
+  {
+    auto exts = E1{};
+    auto strides = std::array<int, 0>{};
+    constexpr auto cr = std::cw<ConversionRule::Conventional>;
+
+    auto m = std::layout_stride::mapping(exts, strides);
+    check_convertible_variants<PaddedLayout, E1, E2, E3>(m, cr);
+  };
+
+  check(std::layout_left_padded<0>{});
+  check(std::layout_left_padded<1>{});
+  check(std::layout_left_padded<2>{});
+  check(std::layout_left_padded<6>{});
+  check(std::layout_left_padded<dyn>{});
+}
+
+constexpr void
+test_from_stride_3d()
 {
   using E1 = std::extents<size_t, 6, 5, 7>;
   using E2 = std::dims<3>;
@@ -215,6 +236,13 @@ test_from_stride()
   check(std::layout_left_padded<2>{});
   check(std::layout_left_padded<6>{});
   check(std::layout_left_padded<dyn>{});
+}
+
+constexpr bool
+test_from_stride()
+{
+  test_from_stride_0d();
+  test_from_stride_3d();
   return true;
 }
 
@@ -226,14 +254,17 @@ test_from_leftpad_0d()
   using E3 = std::extents<uint8_t, 1>;
 
   std::layout_left_padded<6>::mapping<E1> msta{E1{}};
+  std::layout_left_padded<dyn>::mapping<E1> mdyn{E1{}};
 
   auto check = []<typename To>(To, auto m)
   {
-    constexpr auto cr = std::cw<ConversionRule::Always>;
+    constexpr auto cr = std::cw<ConversionRule::Conventional>;
     check_convertible_variants<To, E1, E2, E3>(m, cr);
   };
 
   check(std::layout_left_padded<6>{}, msta);
+  check(std::layout_left_padded<6>{}, msta);
+  check(std::layout_left_padded<dyn>{}, msta);
   check(std::layout_left_padded<dyn>{}, msta);
   return true;
 }
@@ -250,7 +281,7 @@ test_from_leftpad_1d()
 
   auto check = []<typename To>(To, auto m)
   {
-    constexpr auto cr = std::cw<ConversionRule::Always>;
+    constexpr auto cr = std::cw<ConversionRule::Conventional>;
     check_convertible_variants<To, E1, E2, E3>(m, cr);
   };
 
@@ -272,7 +303,7 @@ test_from_leftpad_2d()
   std::layout_left_padded<6>::mapping<E1> msta{E1{}};
   std::layout_left_padded<dyn>::mapping<E1> mdyn{E1{}};
 
-  constexpr auto calways = std::cw<ConversionRule::Always>;
+  constexpr auto cregular = std::cw<ConversionRule::Conventional>;
   constexpr auto cnever = std::cw<ConversionRule::Never>;
 
   auto check = []<typename To>(To, auto m, auto cr)
@@ -280,7 +311,7 @@ test_from_leftpad_2d()
 
   check(std::layout_left_padded<6>{}, msta, cnever);
   check(std::layout_left_padded<6>{}, mdyn, cnever);
-  check(std::layout_left_padded<dyn>{}, msta, calways);
+  check(std::layout_left_padded<dyn>{}, msta, cregular);
   check(std::layout_left_padded<dyn>{}, mdyn, cnever);
   return true;
 }
-- 
2.50.1

Reply via email to