On Thu, 4 Dec 2025 at 12:05, Tomasz Kamiński <[email protected]> wrote:
>
> From: Luc Grosheintz <[email protected]>
>
> 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
> 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, layout_left_padded, layout_right_padded): 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.
>
> Co-authored-by: Tomasz Kamiński <[email protected]>
> Signed-off-by: Luc Grosheintz <[email protected]>
> Signed-off-by: Tomasz Kamiński <[email protected]>
> ---
> v3 rebases it on the trunk, that have both padded layouts,
> and adusjt changes to padded.cc file, as it was refactored.
>
> Tested on x86_64-linux locally. *mdspan* also tested with
> all standard modes. OK for trunk?
OK
>
> libstdc++-v3/include/std/mdspan | 36 +++++---
> .../23_containers/mdspan/layouts/ctors.cc | 82 +++++++++++++++----
> .../23_containers/mdspan/layouts/padded.cc | 13 ++-
> 3 files changed, 96 insertions(+), 35 deletions(-)
>
> diff --git a/libstdc++-v3/include/std/mdspan b/libstdc++-v3/include/std/mdspan
> index bfec288aded..03cc4f02a1c 100644
> --- a/libstdc++-v3/include/std/mdspan
> +++ b/libstdc++-v3/include/std/mdspan
> @@ -935,7 +935,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); }
> @@ -1102,7 +1103,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); }
> @@ -1920,18 +1922,22 @@ _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_storage(__other)
> { __glibcxx_assert(*this == __other); }
>
> - template<typename _LeftpadMapping>
> - requires __mdspan::__is_left_padded_mapping<_LeftpadMapping>
> + template<typename _LeftPaddedMapping>
> + requires __mdspan::__is_left_padded_mapping<_LeftPaddedMapping>
> && is_constructible_v<extents_type,
> - typename _LeftpadMapping::extents_type>
> - constexpr explicit(_S_rank > 1 && (padding_value != dynamic_extent
> - || _LeftpadMapping::padding_value == dynamic_extent))
> - mapping(const _LeftpadMapping& __other)
> + typename _LeftPaddedMapping::extents_type>
> + 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_storage(layout_left{}, __other)
> { }
>
> @@ -2081,7 +2087,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_storage(__other)
> { __glibcxx_assert(*this == __other); }
> @@ -2090,8 +2097,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> requires __mdspan::__is_right_padded_mapping<_RightPaddedMapping>
> && is_constructible_v<extents_type,
> typename
> _RightPaddedMapping::extents_type>
> - constexpr explicit(_S_rank > 1 && (padding_value != dynamic_extent
> - || _RightPaddedMapping::padding_value == dynamic_extent))
> + constexpr explicit(
> + !is_convertible_v<typename _RightPaddedMapping::extents_type,
> + extents_type>
> + || _S_rank > 1 && (padding_value != dynamic_extent
> + || _RightPaddedMapping::padding_value == dynamic_extent))
> mapping(const _RightPaddedMapping& __other)
> : _M_storage(layout_right{}, __other)
> { }
> @@ -2638,7 +2648,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> }
> else
> __glibcxx_assert(__idx <= __ext.extent(0));
> - }
> +}
>
> template<typename _IndexType, size_t _Extent, typename _Slice>
> constexpr void
> diff --git a/libstdc++-v3/testsuite/23_containers/mdspan/layouts/ctors.cc
> b/libstdc++-v3/testsuite/23_containers/mdspan/layouts/ctors.cc
> index 80ae5d8d56a..b6e4138196d 100644
> --- a/libstdc++-v3/testsuite/23_containers/mdspan/layouts/ctors.cc
> +++ b/libstdc++-v3/testsuite/23_containers/mdspan/layouts/ctors.cc
> @@ -239,12 +239,8 @@ namespace from_same_layout
> verify_nothrow_convertible<Layout, std::extents<unsigned int>>(
> std::extents<int>{});
>
> - if constexpr (!is_padded_layout<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>>,
> @@ -254,12 +250,8 @@ namespace from_same_layout
> typename Layout::mapping<std::extents<int, 1>>,
> typename Layout::mapping<std::extents<int>>>();
>
> - if constexpr (!is_padded_layout<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>{});
> @@ -349,6 +341,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 __cplusplus > 202302L
> + 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
> {
> @@ -409,8 +462,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>>(
> @@ -474,5 +526,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 19fdf93ce0d..1b6e063d12d 100644
> --- a/libstdc++-v3/testsuite/23_containers/mdspan/layouts/padded.cc
> +++ b/libstdc++-v3/testsuite/23_containers/mdspan/layouts/padded.cc
> @@ -149,7 +149,6 @@ is_same_mapping(const auto& lhs, const auto& rhs)
> enum class ConversionRule
> {
> Never,
> - Always,
> Regular
> };
>
> @@ -159,8 +158,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>;
> @@ -184,7 +181,7 @@ template<typename LayoutTo, typename Esta, typename Edyn,
> typename Ewrong>
>
> // There's a twist when both mappings are left-padded. There's two
> distinct
> // ctors: a defaulted copy ctor and a constrained template that enables
> - // construction from left-padded mappings even if their layout_type is
> + // construction from left-padded mappings even if their layout_type
> (padding) is
> // different. The two ctors have different rules regarding conversion.
>
> if constexpr (!std::same_as<LayoutTo, LayoutFrom>)
> @@ -329,7 +326,7 @@ template<template<size_t> typename Layout>
>
> auto check = []<typename To>(To, auto m)
> {
> - constexpr auto cr = std::cw<ConversionRule::Always>;
> + constexpr auto cr = std::cw<ConversionRule::Regular>;
> check_convertible_variants<To, E1, E2, E3>(m, cr);
> };
>
> @@ -350,7 +347,7 @@ template<template<size_t> typename Layout>
>
> auto check = []<typename To>(To, auto m)
> {
> - constexpr auto cr = std::cw<ConversionRule::Always>;
> + constexpr auto cr = std::cw<ConversionRule::Regular>;
> check_convertible_variants<To, E1, E2, E3>(m, cr);
> };
>
> @@ -373,7 +370,7 @@ template<template<size_t> typename Layout>
> typename Layout<6>::mapping<E1> msta{E1{}};
> typename Layout<dyn>::mapping<E1> mdyn{E1{}};
>
> - constexpr auto calways = std::cw<ConversionRule::Always>;
> + constexpr auto cregular = std::cw<ConversionRule::Regular>;
> constexpr auto cnever = std::cw<ConversionRule::Never>;
>
> auto check = []<typename To>(To, auto m, auto cr)
> @@ -381,7 +378,7 @@ template<template<size_t> typename Layout>
>
> check(Layout<6>{}, msta, cnever);
> check(Layout<6>{}, mdyn, cnever);
> - check(Layout<dyn>{}, msta, calways);
> + check(Layout<dyn>{}, msta, cregular);
> check(Layout<dyn>{}, mdyn, cnever);
> }
>
> --
> 2.52.0
>