From 90cb588dd134ff93a15151859f7c223b4c665c72 Mon Sep 17 00:00:00 2001 From: Matt Stephanson Date: Fri, 24 Feb 2023 17:25:55 -0800 Subject: [PATCH 1/3] First cut of `mdspan`, known to be incomplete. --- stl/CMakeLists.txt | 1 + stl/inc/__msvc_all_public_headers.hpp | 1 + stl/inc/header-units.json | 1 + stl/inc/mdspan | 899 +++++++++++++ stl/inc/yvals_core.h | 6 + stl/modules/std.ixx | 1 + tests/std/test.lst | 1 + tests/std/tests/P0009R18_mdspan/env.lst | 4 + tests/std/tests/P0009R18_mdspan/test.cpp | 1117 +++++++++++++++++ .../importable_cxx_library_headers.jsonc | 1 + .../test.cpp | 1 + .../test.compile.pass.cpp | 14 + 12 files changed, 2047 insertions(+) create mode 100644 stl/inc/mdspan create mode 100644 tests/std/tests/P0009R18_mdspan/env.lst create mode 100644 tests/std/tests/P0009R18_mdspan/test.cpp diff --git a/stl/CMakeLists.txt b/stl/CMakeLists.txt index 4bc93c646ca..cdac12a0388 100644 --- a/stl/CMakeLists.txt +++ b/stl/CMakeLists.txt @@ -174,6 +174,7 @@ set(HEADERS ${CMAKE_CURRENT_LIST_DIR}/inc/list ${CMAKE_CURRENT_LIST_DIR}/inc/locale ${CMAKE_CURRENT_LIST_DIR}/inc/map + ${CMAKE_CURRENT_LIST_DIR}/inc/mdspan ${CMAKE_CURRENT_LIST_DIR}/inc/memory ${CMAKE_CURRENT_LIST_DIR}/inc/memory_resource ${CMAKE_CURRENT_LIST_DIR}/inc/mutex diff --git a/stl/inc/__msvc_all_public_headers.hpp b/stl/inc/__msvc_all_public_headers.hpp index 1734fa07b26..11950e6310d 100644 --- a/stl/inc/__msvc_all_public_headers.hpp +++ b/stl/inc/__msvc_all_public_headers.hpp @@ -106,6 +106,7 @@ #include #include #include +#include #include #include #include diff --git a/stl/inc/header-units.json b/stl/inc/header-units.json index 612fae95022..270dd1b25ec 100644 --- a/stl/inc/header-units.json +++ b/stl/inc/header-units.json @@ -80,6 +80,7 @@ "list", "locale", "map", + "mdspan", "memory", "memory_resource", "mutex", diff --git a/stl/inc/mdspan b/stl/inc/mdspan new file mode 100644 index 00000000000..34070dbb615 --- /dev/null +++ b/stl/inc/mdspan @@ -0,0 +1,899 @@ +// mdspan standard header + +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#pragma once +#ifndef _MDSPAN_ +#define _MDSPAN_ +#include +#if _STL_COMPILER_PREPROCESSOR +#if !_HAS_CXX23 +_EMIT_STL_WARNING(STL4038, "The contents of are available only with C++23 or later."); +#else // ^^^ !_HAS_CXX23 / _HAS_CXX23 vvv +#include +#include +#include +#include +#include + +#pragma pack(push, _CRT_PACKING) +#pragma warning(push, _STL_WARNING_LEVEL) +#pragma warning(disable : _STL_DISABLED_WARNINGS) +_STL_DISABLE_CLANG_WARNINGS +#pragma push_macro("new") +#undef new + +_STD_BEGIN + +template +struct _Mdspan_extent_type { + using index_type = _IndexType; + + index_type _Dynamic_extents[_Rank_dynamic] = {}; + static constexpr size_t _Static_extents[sizeof...(_Extents)] = {_Extents...}; + static constexpr array _Dynamic_indexes = []() constexpr { + array result; + size_t _Counter = 0; + for (size_t i = 0; i < sizeof...(_Extents); ++i) { + result[i] = _Counter; + if (_Static_extents[i] == dynamic_extent) { + ++_Counter; + } + } + return result; + }(); + + constexpr _Mdspan_extent_type() noexcept = default; + + template && ...), + int> = 0> + constexpr _Mdspan_extent_type(_OtherIndexTypes... _OtherExtents) noexcept : _Dynamic_extents{_OtherExtents...} {} + + template = 0> + constexpr _Mdspan_extent_type(span<_OtherIndexType, _Size> _Data, index_sequence<_Idx...>) noexcept + : _Dynamic_extents{static_cast(_STD as_const(_Data[_Idx]))...} {} + + + template && ...), + int> = 0> + constexpr _Mdspan_extent_type(_OtherIndexTypes... _OtherExtents) noexcept { + auto _It = _Dynamic_extents; + ((_Extents == dynamic_extent ? void(*_It++ = _OtherExtents) : void(_OtherExtents)), ...); + } + + template = 0> + constexpr _Mdspan_extent_type(span<_OtherIndexType, _Size> _Data, index_sequence<_Idx...>) noexcept + : _Dynamic_extents{{static_cast(_STD as_const(_Data[_Dynamic_indexes[_Idx]]))...}} {} + + constexpr index_type* _Begin_dynamic_extents() noexcept { + return _Dynamic_extents; + } + + constexpr const index_type* _Begin_dynamic_extents() const noexcept { + return _Dynamic_extents; + } +}; + +template +struct _Mdspan_extent_type<_IndexType, 0, _Extents...> { + using index_type = _IndexType; + + static constexpr size_t _Static_extents[sizeof...(_Extents)] = {_Extents...}; + + constexpr _Mdspan_extent_type() noexcept = default; + + template = 0> + constexpr _Mdspan_extent_type(_IndexTypes... /*_OtherExtents*/) noexcept {} + + template = 0> + constexpr _Mdspan_extent_type(span<_OtherIndexType, _Size>, index_sequence<_Idx...>) noexcept {} + + constexpr index_type* _Begin_dynamic_extents() noexcept { + return nullptr; + } + + constexpr const index_type* _Begin_dynamic_extents() const noexcept { + return nullptr; + } +}; + +template +struct _Mdspan_extent_type<_IndexType, 0> { + using index_type = _IndexType; + + constexpr index_type* _Begin_dynamic_extents() { + return nullptr; + } + + constexpr const index_type* _Begin_dynamic_extents() const noexcept { + return nullptr; + } +}; + +template +class extents : private _Mdspan_extent_type<_IndexType, ((_Extents == dynamic_extent) + ... + 0), _Extents...> { +public: + using _Mybase = _Mdspan_extent_type<_IndexType, ((_Extents == dynamic_extent) + ... + 0), _Extents...>; + using index_type = typename _Mybase::index_type; + using size_type = make_unsigned_t; + using rank_type = size_t; + + // TRANSITION: doesn't account for extended integer types + static_assert(_Is_any_of_v, signed char, unsigned char, short, unsigned short, int, + unsigned int, long, unsigned long, long long, unsigned long long>, + "N4928 [mdspan.extents.overview]/2 " + "requires that extents::index_type be a signed or unsigned integer type."); + + static_assert(((_Extents == dynamic_extent || _Extents <= (numeric_limits<_IndexType>::max)()) && ...)); + + _NODISCARD static constexpr rank_type rank() noexcept { + return sizeof...(_Extents); + } + + _NODISCARD static constexpr rank_type rank_dynamic() noexcept { + return ((_Extents == dynamic_extent) + ... + 0); + } + + _NODISCARD static constexpr size_t static_extent(const rank_type _Idx) noexcept { + return _Mybase::_Static_extents[_Idx]; + } + + _NODISCARD constexpr index_type extent(const rank_type _Idx) const noexcept { + if constexpr (rank_dynamic() == 0) { + return static_cast(_Mybase::_Static_extents[_Idx]); + } else if constexpr (rank_dynamic() == rank()) { + return _Mybase::_Dynamic_extents[_Idx]; + } else { + const auto _Static_extent = _Mybase::_Static_extents[_Idx]; + if (_Static_extent == dynamic_extent) { + return _Mybase::_Dynamic_extents[_Mybase::_Dynamic_indexes[_Idx]]; + } else { + return static_cast(_Static_extent); + } + } + } + + constexpr extents() noexcept = default; + + template = 0> + explicit((((_Extents != dynamic_extent) && (_OtherExtents == dynamic_extent)) || ...) + || numeric_limits::max() + < numeric_limits<_OtherIndexType>::max()) constexpr extents(const extents<_OtherIndexType, + _OtherExtents...>& _Other) noexcept { + auto _Dynamic_it = _Mybase::_Begin_dynamic_extents(); + for (rank_type _Dim = 0; _Dim < sizeof...(_Extents); ++_Dim) { + if (_Mybase::_Static_extents[_Dim] == dynamic_extent) { + *_Dynamic_it++ = _Other.extent(_Dim); + } + } + } + + template && ...) + && (is_nothrow_constructible_v && ...) + && (sizeof...(_OtherIndexTypes) == rank_dynamic() || sizeof...(_OtherIndexTypes) == rank()), + int> = 0> + explicit constexpr extents(_OtherIndexTypes... _Exts) noexcept + : _Mybase{static_cast(_STD move(_Exts))...} {} + + template + && is_nothrow_constructible_v + && (_Size == rank_dynamic() || _Size == rank()), + int> = 0> + explicit(_Size != rank_dynamic()) constexpr extents(const array<_OtherIndexType, _Size>& _Exts) noexcept + : _Mybase{span{_Exts}, _STD make_index_sequence{}} {} + + template + && is_nothrow_constructible_v + && (_Size == rank_dynamic() || _Size == rank()), + int> = 0> + explicit(_Size != rank_dynamic()) constexpr extents(span<_OtherIndexType, _Size> _Exts) noexcept + : _Mybase{_Exts, _STD make_index_sequence{}} {} + + + template + _NODISCARD_FRIEND constexpr bool operator==( + const extents& _Lhs, const extents<_OtherIndexType, _OtherExtents...>& _Rhs) noexcept { + if constexpr (sizeof...(_Extents) != sizeof...(_OtherExtents)) { + return false; + } + + for (size_t _Dim = 0; _Dim < sizeof...(_Extents); ++_Dim) { + if (_Lhs.extent(_Dim) != _Rhs.extent(_Dim)) { + return false; + } + } + + return true; + } + + constexpr void _Fill_extents(index_type* _Out) const noexcept { + auto _Dynamic_it = _Mybase::_Begin_dynamic_extents(); + for (size_t _Dim = 0; _Dim < sizeof...(_Extents); ++_Dim) { + if (_Mybase::_Static_extents[_Dim] == dynamic_extent) { + *_Out++ = *_Dynamic_it++; + } else { + *_Out++ = static_cast(_Mybase::_Static_extents[_Dim]); + } + } + } +}; + +template +using dextents = + decltype([](const _IndexType2, const index_sequence<_Seq...>) constexpr { + return extents<_IndexType2, ((void) _Seq, dynamic_extent)...>{}; + }(_IndexType{0}, make_index_sequence<_Rank>{})); + +// TRANSITION: why not `((void) _Ext, dynamic_extent)...`?! +template && ...), int> = 0> +extents(_Integrals... _Ext) + -> extents, _Integrals>::value...>; + + +template +constexpr bool _Is_extents = false; + +template +constexpr bool _Is_extents> = true; + + +template +struct _Layout_mapping_alike_helper : false_type {}; + +template +struct _Layout_mapping_alike_helper<_Mapping, + void_t, + is_same, + is_same, bool_constant<_Mapping::is_always_strided()>, + bool_constant<_Mapping::is_always_exhaustive()>, bool_constant<_Mapping::is_always_unique()>>> + : bool_constant<_Is_extents> {}; + +template +struct _Layout_mapping_alike : bool_constant<_Layout_mapping_alike_helper<_Mapping>::value> {}; + + +template +constexpr bool _Is_mapping_of = + is_same_v, _Mapping>; + + +struct layout_left { + template + class mapping; +}; + +struct layout_right { + template + class mapping; +}; + +struct layout_stride { + template + class mapping; +}; + +template +class layout_left::mapping { +public: + using extents_type = _Extents; + using index_type = typename _Extents::index_type; + using size_type = typename _Extents::size_type; + using rank_type = typename _Extents::rank_type; + using layout_type = layout_left; + + constexpr mapping() noexcept = default; + constexpr mapping(const mapping&) noexcept = default; + + constexpr mapping(const _Extents& e) noexcept : _Myext(e){}; + + template , int> = 0> + explicit(!is_convertible_v<_OtherExtents, _Extents>) constexpr mapping( + const mapping<_OtherExtents>& _Other) noexcept + : _Myext{_Other.extents()} {}; + + template , int> = 0> + explicit(!is_convertible_v<_OtherExtents, _Extents>) constexpr mapping( + const layout_right::mapping<_OtherExtents>& _Other) noexcept + : _Myext{_Other.extents()} {} + + template , int> = 0> + explicit(_Extents::rank() > 0) constexpr mapping(const layout_stride::template mapping<_OtherExtents>& _Other) + : _Myext{_Other.extents()} {} + + constexpr mapping& operator=(const mapping&) noexcept = default; + + _NODISCARD constexpr const extents_type& extents() const noexcept { + return _Myext; + } + + _NODISCARD constexpr index_type required_span_size() const noexcept { + index_type _Result = 1; + for (rank_type _Dim = 0; _Dim < _Extents::rank(); ++_Dim) { + _Result *= _Myext.extent(_Dim); + } + + return _Result; + } + + template && ...) + && (is_nothrow_constructible_v && ...), + int> = 0> + _NODISCARD constexpr index_type operator()(_Indices... _Idx) const noexcept { + return _Index_impl...>( + static_cast(_Idx)..., make_index_sequence<_Extents::rank()>{}); + } + + _NODISCARD static constexpr bool is_always_unique() noexcept { + return true; + } + _NODISCARD static constexpr bool is_always_exhaustive() noexcept { + return true; + } + _NODISCARD static constexpr bool is_always_strided() noexcept { + return true; + } + + _NODISCARD constexpr bool is_unique() const noexcept { + return true; + } + _NODISCARD constexpr bool is_exhaustive() const noexcept { + return true; + } + _NODISCARD constexpr bool is_strided() const noexcept { + return true; + } + + template 0), int> = 0> + _NODISCARD constexpr index_type stride(const rank_type _Rank) const noexcept { + index_type _Result = 1; + for (rank_type _Dim = 0; _Dim < _Rank; ++_Dim) { + _Result *= _Myext.extent(_Dim); + } + + return _Result; + } + + template + _NODISCARD friend constexpr bool operator==(const mapping& _Lhs, const mapping& _Rhs) noexcept { + return _Lhs.extents() == _Rhs.extents(); + } + +private: + _Extents _Myext{}; + + template + constexpr index_type _Index_impl(_IndexType... _Idx, index_sequence<_Seq...>) const noexcept { + // return _Extents::rank() > 0 ? ((_Idx * stride(_Seq)) + ... + 0) : 0; + index_type _Stride = 1; + index_type _Result = 0; + (((_Result += _Idx * _Stride), (void) (_Stride *= _Myext.extent(_Seq))), ...); + return _Result; + } +}; + +template +class layout_right::mapping { +public: + using extents_type = _Extents; + using index_type = typename _Extents::index_type; + using size_type = typename _Extents::size_type; + using rank_type = typename _Extents::rank_type; + using layout_type = layout_right; + + constexpr mapping() noexcept = default; + constexpr mapping(const mapping&) noexcept = default; + + constexpr mapping(const _Extents& e) noexcept : _Myext(e){}; + + template , int> = 0> + explicit(!is_convertible_v<_OtherExtents, _Extents>) constexpr mapping( + const mapping<_OtherExtents>& _Other) noexcept + : _Myext{_Other.extents()} {}; + + template , int> = 0> + explicit(!is_convertible_v<_OtherExtents, _Extents>) constexpr mapping( + const layout_left::mapping<_OtherExtents>& _Other) noexcept + : _Myext{_Other.extents()} {} + + template , int> = 0> + explicit(_Extents::rank() > 0) constexpr mapping(const layout_stride::template mapping<_OtherExtents>& _Other) + : _Myext{_Other.extents()} {} + + + constexpr mapping& operator=(const mapping&) noexcept = default; + + _NODISCARD constexpr const extents_type& extents() const noexcept { + return _Myext; + } + + _NODISCARD constexpr index_type required_span_size() const noexcept { + index_type _Result = 1; + for (rank_type _Dim = 0; _Dim < _Extents::rank(); ++_Dim) { + _Result *= _Myext.extent(_Dim); + } + + return _Result; + } + + template && ...) + && (is_nothrow_constructible_v && ...), + int> = 0> + _NODISCARD constexpr index_type operator()(_Indices... _Idx) const noexcept { + return _Index_impl...>( + static_cast(_Idx)..., make_index_sequence<_Extents::rank()>{}); + } + + _NODISCARD static constexpr bool is_always_unique() noexcept { + return true; + } + _NODISCARD static constexpr bool is_always_exhaustive() noexcept { + return true; + } + _NODISCARD static constexpr bool is_always_strided() noexcept { + return true; + } + + _NODISCARD constexpr bool is_unique() const noexcept { + return true; + } + _NODISCARD constexpr bool is_exhaustive() const noexcept { + return true; + } + _NODISCARD constexpr bool is_strided() const noexcept { + return true; + } + + template 0), int> = 0> + _NODISCARD constexpr index_type stride(const rank_type _Rank) const noexcept { + index_type _Result = 1; + for (rank_type _Dim = _Rank + 1; _Dim < _Extents::rank(); ++_Dim) { + _Result *= _Myext.extent(_Dim); + } + + return _Result; + } + + template + _NODISCARD friend constexpr bool operator==(const mapping& _Lhs, const mapping& _Rhs) noexcept { + return _Lhs.extents() == _Rhs.extents(); + } + +private: + _Extents _Myext{}; + + static constexpr size_t _Multiply(size_t _X, size_t _Y) { + return _X * _Y; + } + + template + constexpr index_type _Index_impl(_IndexType... _Idx, index_sequence<_Seq...>) const noexcept { + index_type _Result = 0; + ((void) (_Result = _Idx + _Myext.extent(_Seq) * _Result), ...); + return _Result; + } +}; + + +template +class layout_stride::mapping { +public: + using extents_type = _Extents; + using index_type = typename _Extents::index_type; + using size_type = typename _Extents::size_type; + using rank_type = typename _Extents::rank_type; + using layout_type = layout_stride; + + constexpr mapping() noexcept = default; + constexpr mapping(const mapping&) noexcept = default; + + template , int> = 0> + constexpr mapping(const _Extents& _E_, const array<_OtherIndexType, _Extents::rank()>& _S_) noexcept : _Myext{_E_} { + for (rank_type _Idx = 0; _Idx < _Extents::rank(); ++_Idx) { + _Mystrides[_Idx] = _S_[_Idx]; + } + }; + + template , int> = 0> + constexpr mapping(const _Extents& _E_, const span<_OtherIndexType, _Extents::rank()> _S_) noexcept : _Myext{_E_} { + for (rank_type _Idx = 0; _Idx < _Extents::rank(); ++_Idx) { + _Mystrides[_Idx] = _S_[_Idx]; + } + }; + + template , int> = 0> + explicit(!is_convertible_v<_OtherExtents, _Extents>) constexpr mapping( + const mapping<_OtherExtents>& _Other) noexcept + : _Myext{_Other.extents()}, _Mystrides{_Other.strides()} { + for (rank_type _Idx = 0; _Idx < _Extents::rank(); ++_Idx) { + _Mystrides[_Idx] = _Other.stride(_Idx); + } + } + + template ::value + && is_constructible_v + && _StridedLayoutMapping::is_always_unique() && _StridedLayoutMapping::is_always_strided(), + int> = 0> + explicit( + !is_convertible_v + && (_Is_mapping_of || _Is_mapping_of + || _Is_mapping_of) ) constexpr mapping(const _StridedLayoutMapping& + _Other) noexcept + : _Myext(_Other.extents()) { + for (rank_type _Dim = 0; _Dim < _Extents::rank(); ++_Dim) { + _Mystrides[_Dim] = _Other.stride(_Dim); + } + } + + constexpr mapping& operator=(const mapping&) noexcept = default; + + _NODISCARD constexpr const extents_type& extents() const noexcept { + return _Myext; + } + + _NODISCARD constexpr array strides() const noexcept { + return _Mystrides; + } + + _NODISCARD constexpr index_type required_span_size() const noexcept { + if (_Extents::rank() > 0) { + index_type _Result = 1; + for (rank_type _Dim = 0; _Dim < _Extents::rank(); ++_Dim) { + const auto _Ext = _Myext.extent(_Dim); + if (_Ext == 0) { + return 0; + } + + _Result += (_Ext - 1) * _Mystrides[_Dim]; + } + + return _Result; + } else { + return 1; + } + } + + template && ...) + && (is_nothrow_constructible_v && ...), + int> = 0> + _NODISCARD constexpr index_type operator()(_Indices... _Idx) const noexcept { + return _Index_impl...>( + static_cast(_Idx)..., make_index_sequence<_Extents::rank()>{}); + } + + _NODISCARD static constexpr bool is_always_unique() noexcept { + return true; + } + + _NODISCARD static constexpr bool is_always_exhaustive() noexcept { + return false; + } + + _NODISCARD static constexpr bool is_always_strided() noexcept { + return true; + } + + _NODISCARD constexpr bool is_unique() const noexcept { + return true; + } + + _NODISCARD constexpr bool is_exhaustive() const noexcept { + index_type _Ext_size = 1; + for (rank_type _Dim = 0; _Dim < _Extents::rank(); ++_Dim) { + _Ext_size *= _Myext.extent(_Dim); + } + + return required_span_size() == _Ext_size; + } + + _NODISCARD constexpr bool is_strided() const noexcept { + return true; + } + + template 0), int> = 0> + _NODISCARD constexpr index_type stride(const rank_type _Idx) const noexcept { + return _Mystrides[_Idx]; + } + + template ::value + && extents_type::rank() == _OtherMapping::extents_type::rank() + && _OtherMapping::is_always_strided(), + int> = 0> + _NODISCARD friend constexpr bool operator==(const mapping& _Lhs, const _OtherMapping& _Rhs) noexcept { + if (_Lhs.extents() != _Rhs.extents()) { + return false; + } + + constexpr rank_type _Rank = extents_type::rank(); + for (rank_type _Dim = 0; _Dim < _Rank; ++_Dim) { + if (_Lhs.stride(_Dim) != _Rhs.stride(_Dim)) { + return false; + } + } + + index_type _Offset; + if constexpr (_Rank == 0) { + _Offset = _Rhs(); + } else { + bool _Is_empty = false; + for (rank_type _Dim = 0; _Dim < _Rank; ++_Dim) { + if (_Lhs.extents().extent(_Dim) == 0) { + _Is_empty = true; + break; + } + } + + if (_Is_empty) { + _Offset = 0; + } else { + _Offset = [&_Rhs](index_sequence<_Idx...>) { + return _Rhs(((void) _Idx, 0)...); + } + (make_index_sequence<_Rank>{}); + } + + return _Offset == 0; + } + } + +private: + _Extents _Myext{}; + array _Mystrides = {}; + + template + constexpr index_type _Index_impl(_IndexType... _Idx, index_sequence<_Seq...>) const noexcept { + return ((_Idx * _Mystrides[_Seq]) + ...); + } +}; + +template +struct default_accessor { + using offset_policy = default_accessor; + using element_type = _ElementType; + using reference = _ElementType&; + using data_handle_type = _ElementType*; + + constexpr default_accessor() noexcept = default; + + template ::element_type (*)[], _ElementType (*)[]>, + int> = 0> + constexpr default_accessor(default_accessor<_OtherElementType>) noexcept {} + + _NODISCARD constexpr data_handle_type offset(data_handle_type _Ptr, size_t _Idx) const noexcept { + return _Ptr + _Idx; + } + + _NODISCARD constexpr reference access(data_handle_type _Ptr, size_t _Idx) const noexcept { + return _Ptr[_Idx]; + } +}; + +template > +class mdspan { +public: + // Domain and codomain types + using extents_type = _Extents; + using layout_type = _LayoutPolicy; + using accessor_type = _AccessorPolicy; + using mapping_type = typename layout_type::template mapping; + using element_type = _ElementType; + using value_type = remove_cv_t; + using index_type = typename extents_type::index_type; + using size_type = typename extents_type::size_type; + using rank_type = typename extents_type::rank_type; + using data_handle_type = typename accessor_type::data_handle_type; + using reference = typename accessor_type::reference; + + _NODISCARD static constexpr rank_type rank() noexcept { + return _Extents::rank(); + } + _NODISCARD static constexpr rank_type rank_dynamic() noexcept { + return _Extents::rank_dynamic(); + } + _NODISCARD static constexpr size_t static_extent(const rank_type r) noexcept { + return _Extents::static_extent(r); + } + + template 0) && is_default_constructible_v + && is_default_constructible_v<_Mapping> && is_default_constructible_v, + int> = 0> + constexpr mdspan() {} + constexpr mdspan(const mdspan& rhs) = default; + constexpr mdspan(mdspan&& rhs) = default; + + template && is_default_constructible_v, + int> = 0> + explicit constexpr mdspan(data_handle_type _Ptr_) : _Ptr{_STD move(_Ptr_)}, _Map{extents_type{}} {} + + template 0)) && (is_convertible_v<_OtherIndexTypes, index_type> && ...) + && (is_nothrow_constructible_v && ...) + //&& (sizeof...(_OtherIndexTypes) == rank() || sizeof...(_OtherIndexTypes) == rank_dynamic()) + && is_constructible_v && is_default_constructible_v, + int> = 0> + explicit constexpr mdspan(data_handle_type _Ptr_, _OtherIndexTypes... _Exts) + : _Ptr{_STD move(_Ptr_)}, _Map{extents_type{static_cast(_STD move(_Exts))...}} {} + + template + && is_nothrow_constructible_v + && (_Size == rank() || _Size == rank_dynamic()) + && is_constructible_v && is_default_constructible_v, + int> = 0> + explicit(_Size != rank_dynamic()) constexpr mdspan(data_handle_type _Ptr_, span<_OtherIndexType, _Size>& _Exts) + : _Ptr{_Ptr_}, _Map{_Extents{_Exts}} {} + + template + && is_nothrow_constructible_v + && (_Size == rank() || _Size == rank_dynamic()) + && is_constructible_v && is_default_constructible_v, + int> = 0> + explicit(_Size != rank_dynamic()) constexpr mdspan( + data_handle_type _Ptr_, const array<_OtherIndexType, _Size>& _Exts) + : _Ptr{_Ptr_}, _Map{_Extents{_Exts}} {} + + + template && is_default_constructible_v, int> = 0> + constexpr mdspan(data_handle_type _Ptr_, const _Extents& _Ext) : _Ptr{_Ptr_}, _Map{_Ext} {} + + template , int> = 0> + constexpr mdspan(data_handle_type _Ptr_, const mapping_type& _Map_) : _Ptr{_Ptr_}, _Map{_Map_} {} + + constexpr mdspan(data_handle_type _Ptr_, const mapping_type& _Map_, const accessor_type& _Acc_) + : _Ptr{_Ptr_}, _Map{_Map_}, _Acc{_Acc_} {} + + template &> + && is_constructible_v, + int> = 0> + constexpr explicit(!is_convertible_v&, mapping_type> + || !is_convertible_v) + mdspan(const mdspan<_OtherElementType, _OtherExtents, _OtherLayoutPolicy, _OtherAccessor>& _Other) + : _Ptr{_Other._Ptr}, _Map{_Other._Map}, _Acc{_Other._Acc} { + static_assert(is_constructible_v); + static_assert(is_constructible_v); + } + + constexpr mdspan& operator=(const mdspan& rhs) = default; + constexpr mdspan& operator=(mdspan&& rhs) = default; + + // TRANSITION operator[](const _OtherIndexTypes... _Indices) + template && ...) + && (is_nothrow_constructible_v && ...), + /*&& sizeof...(_OtherIndexTypes) == rank(),*/ + int> = 0> + _NODISCARD constexpr reference operator()(const _OtherIndexTypes... _Indices) const { + return _Acc.access(_Ptr, _Map(static_cast(_STD move(_Indices))...)); + } + + template , int> = 0> + _NODISCARD constexpr reference operator[](span<_OtherIndexType, rank()> _Indices) const { + return _Index_impl(_Indices, make_index_sequence{}); + } + + template , int> = 0> + _NODISCARD constexpr reference operator[](const array<_OtherIndexType, rank()>& _Indices) const { + return _Index_impl(_Indices, make_index_sequence{}); + } + + _NODISCARD constexpr const extents_type& extents() const noexcept { + return _Map.extents(); + } + + _NODISCARD constexpr const data_handle_type& data_handle() const noexcept { + return _Ptr; + } + + _NODISCARD constexpr const mapping_type& mapping() const noexcept { + return _Map; + } + + _NODISCARD constexpr const accessor_type& accessor() const noexcept { + return _Acc; + } + + _NODISCARD constexpr index_type extent(const rank_type r) const noexcept { + const auto& _Ext = _Map.extents(); + return _Ext.extent(r); + } + + _NODISCARD constexpr size_type size() const noexcept { + const auto& _Ext = _Map.extents(); + size_type _Result = 1; + for (rank_type _Dim = 0; _Dim < rank(); ++_Dim) { + _Result *= _Ext.extent(_Dim); + } + return _Result; + } + + _NODISCARD constexpr bool empty() const noexcept { + for (rank_type _Dim = 0; _Dim < rank(); ++_Dim) { + if (_Map.extents().extent(_Dim) == 0) { + return true; + } + } + + return false; + } + + friend constexpr void swap(mdspan& _Lhs, mdspan& _Rhs) noexcept { + swap(_Lhs._Ptr, _Rhs._Ptr); + swap(_Lhs._Map, _Rhs._Map); + swap(_Lhs._Acc, _Rhs._Acc); + } + + _NODISCARD static constexpr bool is_always_unique() { + return mapping_type::is_always_unique(); + } + + _NODISCARD static constexpr bool is_always_exhaustive() { + return mapping_type::is_always_exhaustive(); + } + + _NODISCARD static constexpr bool is_always_strided() { + return mapping_type::is_always_strided(); + } + + _NODISCARD constexpr bool is_unique() const { + return _Map.is_unique(); + } + + _NODISCARD constexpr bool is_exhaustive() const { + return _Map.is_exhaustive(); + } + + _NODISCARD constexpr bool is_strided() const { + return _Map.is_strided(); + } + + _NODISCARD constexpr index_type stride(const size_t _Dim) const { + return _Map.stride(_Dim); + } + +private: + template + _NODISCARD constexpr reference _Index_impl(_IndexContainer&& _Indices, index_sequence<_Idx...>) const { + return _Acc.access(_Ptr, _Map(_STD as_const(_STD forward<_IndexContainer>(_Indices)[_Idx])...)); + } + + data_handle_type _Ptr{}; + mapping_type _Map{}; + accessor_type _Acc{}; +}; + +_STD_END + +#pragma pop_macro("new") +_STL_RESTORE_CLANG_WARNINGS +#pragma warning(pop) +#pragma pack(pop) +#endif // _HAS_CXX23 +#endif // _STL_COMPILER_PREPROCESSOR +#endif // _MDSPAN_ diff --git a/stl/inc/yvals_core.h b/stl/inc/yvals_core.h index 85bebe3ffbc..f1304b2f1da 100644 --- a/stl/inc/yvals_core.h +++ b/stl/inc/yvals_core.h @@ -299,6 +299,7 @@ // Other C++20 deprecation warnings // _HAS_CXX23 directly controls: +// P0009R18 // P0288R9 move_only_function // P0323R12 // P0401R6 Providing Size Feedback In The Allocator Interface @@ -352,6 +353,10 @@ // P2499R0 string_view Range Constructor Should Be explicit // P2505R5 Monadic Functions For expected // P2549R1 unexpected::error() +// P2599R2 mdspan: index_type, size_type +// P2604R0 mdspan: data_handle_type, data_handle(), exhaustive +// P2613R1 mdspan: empty() +// P2763R1 Fixing layout_stride's Default Constructor For Fully Static Extents // _HAS_CXX23 and _SILENCE_ALL_CXX23_DEPRECATION_WARNINGS control: // P1413R3 Deprecate aligned_storage And aligned_union @@ -1707,6 +1712,7 @@ _EMIT_STL_ERROR(STL1004, "C++98 unexpected() is incompatible with C++23 unexpect #define __cpp_lib_invoke_r 202106L #define __cpp_lib_ios_noreplace 202207L #define __cpp_lib_is_scoped_enum 202011L +#define __cpp_lib_mdspan 202207L #if !defined(__clang__) && !defined(__EDG__) // TRANSITION, Clang and EDG support for modules #define __cpp_lib_modules 202207L diff --git a/stl/modules/std.ixx b/stl/modules/std.ixx index dc43b5722e6..e56d32f850f 100644 --- a/stl/modules/std.ixx +++ b/stl/modules/std.ixx @@ -76,6 +76,7 @@ export module std; #include #include #include +#include #include #include #include diff --git a/tests/std/test.lst b/tests/std/test.lst index 22e1b45d8f6..26d1288ae75 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -227,6 +227,7 @@ tests\LWG3422_seed_seq_ctors tests\LWG3480_directory_iterator_range tests\LWG3545_pointer_traits_sfinae tests\LWG3610_iota_view_size_and_integer_class +tests\P0009R18_mdspan tests\P0019R8_atomic_ref tests\P0024R2_parallel_algorithms_adjacent_difference tests\P0024R2_parallel_algorithms_adjacent_find diff --git a/tests/std/tests/P0009R18_mdspan/env.lst b/tests/std/tests/P0009R18_mdspan/env.lst new file mode 100644 index 00000000000..9c93b590ac7 --- /dev/null +++ b/tests/std/tests/P0009R18_mdspan/env.lst @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +RUNALL_INCLUDE ..\usual_latest_matrix.lst \ No newline at end of file diff --git a/tests/std/tests/P0009R18_mdspan/test.cpp b/tests/std/tests/P0009R18_mdspan/test.cpp new file mode 100644 index 00000000000..f528252183d --- /dev/null +++ b/tests/std/tests/P0009R18_mdspan/test.cpp @@ -0,0 +1,1117 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include + +using namespace std; + +#ifdef __cpp_lib_concepts +// A type that's regular and trivially copyable, and also maximally nothrow. +template +using is_regular_trivial_nothrow = std::conjunction>, is_trivially_copyable, + is_nothrow_default_constructible, is_nothrow_copy_constructible, is_nothrow_move_constructible, + is_nothrow_copy_assignable, is_nothrow_move_assignable, is_nothrow_swappable>; + +template +inline constexpr bool is_regular_trivial_nothrow_v = is_regular_trivial_nothrow::value; +#endif // __cpp_lib_concepts + +struct Constructible { + // noexcept constructible for size_t, but not convertible + explicit operator size_t() noexcept; +}; + +struct Convertible { + // convertible, but not noexcept constructible + operator size_t(); +}; + +struct ConstructibleAndConvertible { + // convertible and noexcept constuctible + constexpr operator size_t() noexcept { + return size_t{0}; + }; +}; + +struct ConstructibleAndConvertibleConst { + // convertible and noexcept constuctible + constexpr operator size_t() const noexcept { + return size_t{0}; + }; +}; + + +void extent_tests_traits() { +#ifdef __cpp_lib_concepts + static_assert(is_regular_trivial_nothrow_v>); + static_assert(is_regular_trivial_nothrow_v>); + static_assert(is_regular_trivial_nothrow_v>); + static_assert(is_regular_trivial_nothrow_v>); + static_assert(is_regular_trivial_nothrow_v>); +#endif // __cpp_lib_concepts + + static_assert(is_same_v, extents>); + static_assert(is_same_v, extents>); + static_assert(is_same_v, extents>); + static_assert(is_same_v, extents>); + + constexpr extents e(2, 3); + static_assert(is_same_v, dextents>); + static_assert(e.static_extent(0) == dynamic_extent); + static_assert(e.static_extent(1) == dynamic_extent); + static_assert(e.extent(0) == 2); + static_assert(e.extent(1) == 3); + + constexpr dextents ex0(1); + (void) ex0; + static_assert(!is_constructible_v, int*>); + + extents(); + extents(); + extents(); + extents(); + extents(); + extents(); + extents(); + extents(); + extents(); + extents(); +} + +void extent_tests_rank() { + static_assert(extents::rank() == 0); + static_assert(extents::rank_dynamic() == 0); + + static_assert(extents::rank() == 1); + static_assert(extents::rank_dynamic() == 0); + + static_assert(extents::rank() == 1); + static_assert(extents::rank_dynamic() == 1); + + static_assert(extents::rank() == 2); + static_assert(extents::rank_dynamic() == 0); + + static_assert(extents::rank() == 2); + static_assert(extents::rank_dynamic() == 1); + + static_assert(extents::rank() == 2); + static_assert(extents::rank_dynamic() == 1); + + static_assert(extents::rank() == 2); + static_assert(extents::rank_dynamic() == 2); +} + +void extent_tests_static_extent() { + static_assert(extents::static_extent(0) == 2); + static_assert(extents::static_extent(1) == 3); + + static_assert(extents::static_extent(0) == 2); + static_assert(extents::static_extent(1) == dynamic_extent); + + static_assert(extents::static_extent(0) == dynamic_extent); + static_assert(extents::static_extent(1) == 3); + + static_assert(extents::static_extent(0) == dynamic_extent); + static_assert(extents::static_extent(1) == dynamic_extent); +} + +void extent_tests_extent() { + constexpr extents e_23; + static_assert(e_23.extent(0) == 2); + static_assert(e_23.extent(1) == 3); + + constexpr extents e_2d{3}; + static_assert(e_2d.extent(0) == 2); + static_assert(e_2d.extent(1) == 3); + + constexpr extents e_dd{2, 3}; + static_assert(e_dd.extent(0) == 2); + static_assert(e_dd.extent(1) == 3); + + constexpr extents e_2dd{3, 5}; + static_assert(e_2dd.extent(0) == 2); + static_assert(e_2dd.extent(1) == 3); + static_assert(e_2dd.extent(2) == 5); +} + +void extent_tests_ctor_other_sizes() { + static_assert(!is_constructible_v, Constructible>); + static_assert(!is_constructible_v, Convertible>); + // static_assert(is_constructible_v, ConstructibleAndConvertible>); + constexpr extents ex0{ConstructibleAndConvertible{}}; + // static_assert(is_constructible_v, ConstructibleAndConvertibleConst>); + constexpr extents ex1{ConstructibleAndConvertibleConst{}}; + + // static_assert(is_constructible_v, int>); + constexpr extents ex2(1); + static_assert(!is_constructible_v, int, int>); + // static_assert(is_constructible_v, int, int, int>); + extents ex3(1, 2, 3); + + (void) ex0; + (void) ex1; + (void) ex2; + (void) ex3; + + extents e0; + assert(e0.extent(0) == 2); + assert(e0.extent(1) == 3); + + extents e1(5); + assert(e1.extent(0) == 2); + assert(e1.extent(1) == 5); + + extents e2(5); + assert(e2.extent(0) == 5); + assert(e2.extent(1) == 3); + + extents e3(5, 7); + assert(e3.extent(0) == 5); + assert(e3.extent(1) == 7); +} + +void extent_tests_copy_ctor_other() { + // Rank and value of static extents must match. + static_assert(!is_constructible_v, extents>); + static_assert(!is_constructible_v, extents>); + static_assert(!is_constructible_v, extents>); + + // Static extents are constuctible, but not convertible, from dynamic extents. + // static_assert(is_constructible_v, extents>); + constexpr extents ex0{extents{}}; + (void) ex0; + static_assert(!is_convertible_v, extents>); + + // Dynamic extents are constuctible and convertible from static extents. + static_assert(is_constructible_v, extents>); + extents{extents{}}; + static_assert(is_convertible_v, extents>); + + // Can implicitly convert from narrower to wider size_type, but not vice-versa. + static_assert(is_convertible_v, extents>); + static_assert(!is_convertible_v, extents>); + + extents e_dyn(3); + extents e(e_dyn); + (void) e; + + using E = extents; + + extents e0{extents{}}; + E e1(extents(2u)); + extents e2{extents{3u}}; + extents e3{extents{2u, 3u}}; + + (void) e0; + (void) e1; + (void) e2; + (void) e3; +} + +template +struct is_array_cons_avail : std::false_type {}; + +template +struct is_array_cons_avail>()}), T>::value>> : std::true_type { +}; + +template +constexpr bool is_array_cons_avail_v = is_array_cons_avail::value; + +void extent_tests_ctor_array() { + static_assert(!is_constructible_v, array>); + static_assert(!is_constructible_v, array>); + static_assert(!is_constructible_v, array>); + static_assert(is_constructible_v, array>); + constexpr extents ex0{array{}}; + (void) ex0; + + static_assert(is_constructible_v, array>); + constexpr extents ex1{array{}}; + static_assert(!is_constructible_v, array>); + static_assert(is_constructible_v, array>); + constexpr extents ex2{array{1, 2, 2}}; + (void) ex1; + (void) ex2; + + static_assert(is_constructible_v, array>); + constexpr extents ex3{array{}}; + static_assert(is_constructible_v, array>); + constexpr extents ex4{array{}}; + static_assert(!is_constructible_v, array>); + (void) ex3; + (void) ex4; + + extents e0; + assert(e0.extent(0) == 2u); + assert(e0.extent(1) == 3u); + + // native extent::size_type + extents e1(to_array({5})); + assert(e1.extent(0) == 2u); + assert(e1.extent(1) == 5u); + + extents e2(to_array({5})); + assert(e2.extent(0) == 5u); + assert(e2.extent(1) == 3u); + + extents e3(to_array({5, 7})); + assert(e3.extent(0) == 5u); + assert(e3.extent(1) == 7u); + + // convertible size type + extents e4(to_array({5})); + assert(e4.extent(0) == 2u); + assert(e4.extent(1) == 5u); + + extents e5(to_array({5})); + assert(e5.extent(0) == 5u); + assert(e5.extent(1) == 3u); + + extents e6(to_array({5, 7})); + assert(e6.extent(0) == 5u); + assert(e6.extent(1) == 7u); +} + +void extent_tests_ctor_span() { + static_assert(!is_constructible_v, span>); + static_assert(!is_constructible_v, span>); + static_assert(!is_constructible_v, span>); + static_assert(is_constructible_v, span>); + ConstructibleAndConvertibleConst arr0[1] = {{}}; + constexpr extents ex0{span{arr0}}; + (void) ex0; + + static_assert(is_constructible_v, span>); + constexpr int arr1[1] = {1}; + constexpr extents ex1{span{arr1}}; + static_assert(!is_constructible_v, span>); + static_assert(is_constructible_v, span>); + constexpr int arr2[3] = {3, 2, 2}; + constexpr extents ex2{span{arr2}}; + (void) ex1; + (void) ex2; + + + extents e0; + assert(e0.extent(0) == 2u); + assert(e0.extent(1) == 3u); + + // native extent::size_type + constexpr int one_int[] = {5}; + constexpr int two_int[] = {5, 7}; + extents e1(span{one_int}); + assert(e1.extent(0) == 2); + assert(e1.extent(1) == 5); + + extents e2(span{one_int}); + assert(e2.extent(0) == 5); + assert(e2.extent(1) == 3); + + extents e3(span{two_int}); + assert(e3.extent(0) == 5); + assert(e3.extent(1) == 7); + + // convertible size type + constexpr size_t one_sizet[] = {5}; + constexpr size_t two_sizet[] = {5, 7}; + extents e4(span{one_sizet}); + assert(e4.extent(0) == 2); + assert(e4.extent(1) == 5); + + extents e5(span{one_sizet}); + assert(e5.extent(0) == 5); + assert(e5.extent(1) == 3); + + extents e6(span{two_sizet}); + assert(e6.extent(0) == 5); + assert(e6.extent(1) == 7); +} + +void extent_tests_equality() { + static_assert(extents{} == extents{}); + static_assert(extents{} != extents{}); + static_assert(extents{} != extents{}); + + extents e_23; + extents e_2d{3}; + extents e_d3{2}; + extents e_dd{2, 3}; + + assert(e_23 == e_2d); + assert(e_23 == e_d3); + assert(e_23 == e_dd); + assert(e_2d == e_d3); +} + +template = 0> +void TestMapping(const Mapping& map) { + using IndexT = typename Mapping::index_type; + using RankT = typename Mapping::rank_type; + static_assert(is_same_v()(IndexT{0}, IndexT{0}))>); + static_assert(is_same_v().stride(RankT{0}))>); + + array s; + const auto& e = map.extents(); + size_t num_entries = 1; + for (size_t i = 0; i < Mapping::extents_type::rank(); ++i) { + num_entries *= e.extent(i); + s[i] = map.stride(i); + } + + vector indices; + indices.reserve(num_entries); + + for (IndexT i = 0; i < e.extent(0); ++i) { + for (IndexT j = 0; j < e.extent(1); ++j) { + const auto idx = i * s[0] + j * s[1]; + assert(map(i, j) == idx); + indices.push_back(idx); + } + } + + bool is_unique = true; + bool is_exhaust = true; + sort(indices.begin(), indices.end()); + for (size_t i = 1; i < indices.size(); ++i) { + const auto diff = indices[i] - indices[i - 1]; + if (diff == 0) { + is_unique = false; + } else if (diff != 1) { + is_exhaust = false; + } + } + + assert(map.is_unique() == is_unique); + assert(map.is_exhaustive() == is_exhaust); + assert(map.required_span_size() == indices.back() + 1); +} + +template = 0> +void TestMapping(const Mapping& map) { + using IndexT = typename Mapping::index_type; + using RankT = typename Mapping::rank_type; + static_assert(is_same_v()(IndexT{0}, IndexT{0}, IndexT{0}))>); + static_assert(is_same_v().stride(RankT{0}))>); + + array s; + const auto& e = map.extents(); + size_t num_entries = 1; + for (size_t i = 0; i < Mapping::extents_type::rank(); ++i) { + num_entries *= e.extent(i); + s[i] = map.stride(i); + } + + vector indices; + indices.reserve(num_entries); + + for (IndexT i = 0; i < e.extent(0); ++i) { + for (IndexT j = 0; j < e.extent(1); ++j) { + for (IndexT k = 0; k < e.extent(2); ++k) { + const auto idx = i * s[0] + j * s[1] + k * s[2]; + assert(map(i, j, k) == idx); + indices.push_back(idx); + } + } + } + + bool is_unique = true; + bool is_exhaust = true; + sort(indices.begin(), indices.end()); + for (size_t i = 1; i < indices.size(); ++i) { + const auto diff = indices[i] - indices[i - 1]; + if (diff == 0) { + is_unique = false; + } else if (diff != 1) { + is_exhaust = false; + } + } + + assert(map.is_unique() == is_unique); + assert(map.is_exhaustive() == is_exhaust); + assert(map.required_span_size() == indices.back() + 1); +} + +void layout_left_tests_traits() { +#ifdef __cpp_lib_concepts + static_assert(is_regular_trivial_nothrow_v>>); + static_assert(is_regular_trivial_nothrow_v>>); + static_assert(is_regular_trivial_nothrow_v>>); + static_assert(is_regular_trivial_nothrow_v>>); +#endif // __cpp_lib_concepts + + using E = extents; + static_assert(is_same_v::extents_type, E>); + static_assert(is_same_v::index_type, E::index_type>); + static_assert(is_same_v::size_type, E::size_type>); + static_assert(is_same_v::rank_type, E::rank_type>); + static_assert(is_same_v::layout_type, layout_left>); +} + +void layout_right_tests_traits() { +#ifdef __cpp_lib_concepts + static_assert(is_regular_trivial_nothrow_v>>); + static_assert(is_regular_trivial_nothrow_v>>); + static_assert(is_regular_trivial_nothrow_v>>); + static_assert(is_regular_trivial_nothrow_v>>); +#endif // __cpp_lib_concepts + + using E = extents; + static_assert(is_same_v::extents_type, E>); + static_assert(is_same_v::index_type, E::index_type>); + static_assert(is_same_v::size_type, E::size_type>); + static_assert(is_same_v::rank_type, E::rank_type>); + static_assert(is_same_v::layout_type, layout_right>); +} + +void layout_stride_tests_traits() { +#ifdef __cpp_lib_concepts + static_assert(is_regular_trivial_nothrow_v>>); + static_assert(is_regular_trivial_nothrow_v>>); + static_assert(is_regular_trivial_nothrow_v>>); + static_assert( + is_regular_trivial_nothrow_v>>); +#endif // __cpp_lib_concepts + + using E = extents; + static_assert(is_same_v::extents_type, E>); + static_assert(is_same_v::index_type, E::index_type>); + static_assert(is_same_v::size_type, E::size_type>); + static_assert(is_same_v::rank_type, E::rank_type>); + static_assert(is_same_v::layout_type, layout_stride>); +} + +void layout_left_tests_properties() { + constexpr layout_left::mapping> map{}; + static_assert(map.is_unique() == true); + static_assert(map.is_exhaustive() == true); + static_assert(map.is_strided() == true); + + static_assert(decltype(map)::is_always_unique() == true); + static_assert(decltype(map)::is_always_exhaustive() == true); + static_assert(decltype(map)::is_always_strided() == true); +} + +void layout_right_tests_properties() { + constexpr layout_right::mapping> map{}; + static_assert(map.is_unique() == true); + static_assert(map.is_exhaustive() == true); + static_assert(map.is_strided() == true); + + static_assert(decltype(map)::is_always_unique() == true); + static_assert(decltype(map)::is_always_exhaustive() == true); + static_assert(decltype(map)::is_always_strided() == true); +} + +void layout_stride_tests_properties() { + constexpr layout_stride::mapping> map{}; + static_assert(map.is_unique() == true); + static_assert(map.is_strided() == true); + + static_assert(decltype(map)::is_always_unique() == true); + static_assert(decltype(map)::is_always_exhaustive() == false); + static_assert(decltype(map)::is_always_strided() == true); +} + +void layout_left_tests_extents_ctor() { + constexpr extents e1; + constexpr extents e2{7}; + constexpr extents e3{11}; + constexpr extents e4{17, 19}; + + constexpr layout_left::mapping m1{e1}; + static_assert(m1.extents() == e1); + + constexpr layout_left::mapping m2{e2}; + static_assert(m2.extents() == e2); + + constexpr layout_left::mapping m3{e3}; + static_assert(m3.extents() == e3); + + constexpr layout_left::mapping m4{e4}; + static_assert(m4.extents() == e4); +} + +void layout_right_tests_extents_ctor() { + constexpr extents e1; + constexpr extents e2{7}; + constexpr extents e3{11}; + constexpr extents e4{17, 19}; + + constexpr layout_right::mapping m1{e1}; + static_assert(m1.extents() == e1); + + constexpr layout_right::mapping m2{e2}; + static_assert(m2.extents() == e2); + + constexpr layout_right::mapping m3{e3}; + static_assert(m3.extents() == e3); + + constexpr layout_right::mapping m4{e4}; + static_assert(m4.extents() == e4); +} + +void layout_stride_tests_extents_ctor() { + constexpr extents e1; + constexpr array s1{1, 2}; + + constexpr layout_stride::mapping m1{e1, s1}; + static_assert(m1.extents() == e1); + static_assert(m1.strides() == s1); + + constexpr extents e2{7}; + constexpr array s2{7, 1}; + + constexpr layout_stride::mapping m2{e2, s2}; + static_assert(m2.extents() == e2); + static_assert(m2.strides() == s2); +} + +template +void copy_ctor_helper_left(const Extents& e) { + const layout_left::mapping m1{e}; + const layout_left::mapping m2{m1}; + assert(m1 == m2); +} + +template +void copy_ctor_helper_right(const Extents& e) { + const layout_right::mapping m1{e}; + const layout_right::mapping m2{m1}; + assert(m1 == m2); +} + +void layout_left_tests_copy_ctor() { + copy_ctor_helper_left(extents{}); + copy_ctor_helper_left(extents{7}); + copy_ctor_helper_left(extents{11}); + copy_ctor_helper_left(extents{17, 19}); +} + +void layout_right_tests_copy_ctor() { + copy_ctor_helper_right(extents{}); + copy_ctor_helper_right(extents{7}); + copy_ctor_helper_right(extents{11}); + copy_ctor_helper_right(extents{17, 19}); +} + +void layout_left_tests_copy_other_extent() { + using E1 = extents; + using E2 = extents; + constexpr E1 e1; + constexpr E2 e2{3}; + constexpr layout_left::mapping m1(static_cast(e2)); + constexpr layout_left::mapping m2(e1); + + static_assert(m1.extents() == e1); + static_assert(m2.extents() == e1); + static_assert(m1.extents() == e2); + static_assert(m2.extents() == e2); +} + +void layout_right_tests_copy_ctor_other() { + using E1 = extents; + using E2 = extents; + constexpr E1 e1; + constexpr E2 e2{3}; + constexpr layout_right::mapping m1(static_cast(e2)); + constexpr layout_right::mapping m2(e1); + + static_assert(m1.extents() == e1); + static_assert(m2.extents() == e1); + static_assert(m1.extents() == e2); + static_assert(m2.extents() == e2); +} + +template +void assign_helper_left(const Extents& e) { + const layout_left::mapping m1{e}; + layout_left::mapping m2; + m2 = m1; + assert(m1 == m2); +} + +template +void assign_helper_right(const Extents& e) { + const layout_right::mapping m1{e}; + layout_right::mapping m2; + m2 = m1; + assert(m1 == m2); +} + +void layout_left_tests_assign() { + assign_helper_left(extents{}); + assign_helper_left(extents{7}); + assign_helper_left(extents{11}); + assign_helper_left(extents{17, 19}); +} + +void layout_right_tests_assign() { + assign_helper_right(extents{}); + assign_helper_right(extents{7}); + assign_helper_right(extents{11}); + assign_helper_right(extents{17, 19}); +} + +void layout_left_tests_ctor_other_layout() { + using E = extents; + + // from layout_left + using OE1 = extents; + static_assert(is_nothrow_constructible_v, layout_right::mapping>); + static_assert(is_nothrow_convertible_v, layout_left::mapping>); + + using OE2 = extents; // not convertible + static_assert(is_nothrow_constructible_v, layout_right::mapping>); + static_assert(!is_convertible_v, layout_left::mapping>); + + using OE3 = extents; // not constructible, rank > 1 + static_assert(!is_constructible_v, layout_right::mapping>); + static_assert(!is_convertible_v, layout_left::mapping>); + + static_assert(!is_constructible_v, layout_right::mapping>); + static_assert(!is_convertible_v, layout_left::mapping>); + + // from layout_stride + static_assert(is_constructible_v>, layout_stride::mapping>>); + static_assert(is_convertible_v>, layout_left::mapping>>); + + static_assert(is_constructible_v, layout_stride::mapping>); + static_assert(!is_convertible_v, layout_left::mapping>); + + static_assert(!is_constructible_v, layout_stride::mapping>); + static_assert(!is_convertible_v, layout_left::mapping>); +} + +void layout_right_tests_ctor_other_layout() { + using E = extents; + + // from layout_left + using OE1 = extents; + static_assert(is_nothrow_constructible_v, layout_left::mapping>); + static_assert(is_nothrow_convertible_v, layout_right::mapping>); + + using OE2 = extents; // not convertible + static_assert(is_nothrow_constructible_v, layout_left::mapping>); + static_assert(!is_convertible_v, layout_right::mapping>); + + using OE3 = extents; // not constructible, rank > 1 + static_assert(!is_constructible_v, layout_left::mapping>); + static_assert(!is_convertible_v, layout_right::mapping>); + + static_assert(!is_constructible_v, layout_left::mapping>); + static_assert(!is_convertible_v, layout_right::mapping>); + + // from layout_stride + static_assert(is_constructible_v>, layout_stride::mapping>>); + static_assert(is_convertible_v>, layout_right::mapping>>); + + static_assert(is_constructible_v, layout_stride::mapping>); + static_assert(!is_convertible_v, layout_right::mapping>); + + static_assert(!is_constructible_v, layout_stride::mapping>); + static_assert(!is_convertible_v, layout_right::mapping>); +} + +void layout_left_tests_strides() { + using E = extents; + layout_left::mapping map; + static_assert(map.stride(0) == 1); + static_assert(map.stride(1) == 2); + static_assert(map.stride(2) == 2 * 3); + static_assert(map.stride(3) == 2 * 3 * 5); +} + +void layout_right_tests_strides() { + using E = extents; + layout_right::mapping map; + static_assert(map.stride(0) == 7 * 5 * 3); + static_assert(map.stride(1) == 7 * 5); + static_assert(map.stride(2) == 7); + static_assert(map.stride(3) == 1); +} + +void layout_stride_tests_strides() { + using E = extents; + constexpr array s{1, 3}; + constexpr layout_stride::mapping map{E{}, s}; + static_assert(map.stride(0) == s[0]); + static_assert(map.stride(1) == s[1]); + static_assert(map.strides() == s); +} + +void layout_left_tests_indexing() { + static_assert(layout_left::mapping>{}() == 0); + TestMapping(layout_left::mapping>{}); + TestMapping(layout_left::mapping>{}); +} + +void layout_right_tests_indexing() { + static_assert(layout_right::mapping>{}() == 0); + TestMapping(layout_right::mapping>{}); + TestMapping(layout_right::mapping>{}); +} + +template +void copy_ctor_helper_stride(const Extents& e) { + const layout_stride::mapping m1{layout_right::mapping{e}}; + const layout_stride::mapping m2{m1}; + assert(m1 == m2); +} + +void layout_stride_tests_copy_ctor() { + copy_ctor_helper_stride(extents{}); + copy_ctor_helper_stride(extents{7}); + copy_ctor_helper_stride(extents{11}); + copy_ctor_helper_stride(extents{17, 19}); +} + +void layout_stride_tests_ctor_other_extents() { + constexpr extents e1; + constexpr extents e2{3}; + constexpr array s{3, 1}; + + constexpr layout_stride::mapping m1(e1, s); + constexpr layout_stride::mapping m2(m1); + + static_assert(m2.extents() == e1); + static_assert(m2.strides() == s); +} + +template +void other_mapping_helper() { + constexpr LayoutMapping other; + constexpr layout_stride::mapping map{other}; + static_assert(map.extents() == other.extents()); + for (size_t i = 0; i < LayoutMapping::extents_type::rank(); ++i) { + assert(map.stride(i) == other.stride(i)); + } +} +void layout_stride_tests_ctor_other_mapping() { + using E = extents; + other_mapping_helper>(); + other_mapping_helper>(); +} + +template +void assign_helper_stride(const Extents& e) { + const layout_stride::mapping m1{layout_right::mapping{e}}; + layout_stride::mapping m2; + m2 = m1; + assert(m1 == m2); +} + +void layout_stride_tests_assign() { + assign_helper_stride(extents{}); + assign_helper_stride(extents{7}); + assign_helper_stride(extents{11}); + assign_helper_stride(extents{17, 19}); +} + +void layout_stride_tests_indexing_static() { + using E = extents; + TestMapping(layout_stride::mapping{E{}, array{1, 2}}); + TestMapping(layout_stride::mapping{E{}, array{3, 1}}); + + // non-exhaustive mappings + TestMapping(layout_stride::mapping{E{}, array{1, 3}}); + TestMapping(layout_stride::mapping{E{}, array{4, 1}}); + TestMapping(layout_stride::mapping{E{}, array{2, 3}}); + + // exhaustive mappings with singleton dimensions + using E1 = extents; + TestMapping(layout_stride::mapping{E1{}, array{3, 1, 1}}); + TestMapping(layout_stride::mapping{E1{}, array{3, 7, 1}}); + + using E2 = extents; + TestMapping(layout_stride::mapping{E2{}, array{3, 1, 1}}); + TestMapping(layout_stride::mapping{E2{}, array{3, 1, 11}}); + + using E3 = extents; + TestMapping(layout_stride::mapping{E3{}, array{1, 3, 1}}); + TestMapping(layout_stride::mapping{E3{}, array{1, 3, 13}}); +} + + +void layout_stride_tests_equality() { + using E = extents; + constexpr layout_stride::mapping map1(layout_right::mapping{}); + constexpr layout_stride::mapping map2(layout_right::mapping{}); + static_assert(map1 == map2); + + constexpr layout_stride::mapping map3{layout_left::mapping{}}; + static_assert(map1 != map3); + + using ED = extents; + constexpr layout_stride::mapping map4{ED{2, 3}, map1.strides()}; + static_assert(map1 == map4); +} + +void accessor_tests_general() { + default_accessor a; + double arr[4] = {}; + static_assert(a.offset(arr, 3) == &arr[3]); + + a.access(arr, 2) = 42; + assert(arr[2] == 42); + + static_assert(is_constructible_v, default_accessor>); + static_assert(!is_constructible_v, default_accessor>); +} + +namespace Pathological { + + struct Empty {}; + + struct Extents { + using index_type = int; + using size_type = std::make_unsigned_t; + using rank_type = size_t; + + explicit Extents(Empty) {} + + template + explicit Extents(const array&) {} + + static constexpr size_t rank() { + return 0; + } + + static constexpr size_t rank_dynamic() { + return 0; + } + }; + + struct Layout { + template + struct mapping { + using extents_type = E; + using layout_type = Layout; + mapping(Extents) {} + }; + }; + + struct Accessor { + using data_handle_type = int*; + using reference = int&; + Accessor(int) {} + }; +} // namespace Pathological + +void mdspan_tests_traits() { + using M = mdspan>; + static_assert(is_trivially_copyable_v /*&& is_default_constructible_v*/ + && is_copy_constructible_v && is_move_constructible_v && is_copy_assignable_v + && is_move_assignable_v); + + static_assert(is_same_v>); + static_assert(is_same_v); + static_assert(is_same_v>); + static_assert(is_same_v>>); + static_assert(is_same_v); + static_assert(is_same_v); + static_assert(is_same_v); + static_assert(is_same_v); + static_assert(is_same_v); +} + +void mdspan_tests_ctor_sizes() { + static constexpr int arr[6] = {}; + constexpr mdspan> mds1(arr, 2); + static_assert(mds1.data_handle() == arr); + static_assert((mds1.extents() == extents{})); + static_assert(mds1.is_exhaustive()); + + static_assert(!is_constructible_v, int*, + Pathological::Empty>); // Empty not convertible to size_type + + // TRANSITION: this assert should be true + // static_assert(!is_constructible_v, int*, + // int>); // Pathological::Extents not constructible from int + + static_assert(!is_constructible_v, Pathological::Layout>, int*, + int>); // Pathological::Layout not constructible from extents + + static_assert( + !is_constructible_v, layout_right, Pathological::Accessor>, int*, + int>); // Pathological::Accessor not default constructible +} + +void mdspan_tests_ctor_array() { + static constexpr int arr[6] = {}; + constexpr mdspan> mds1(arr, array{2}); + static_assert(mds1.data_handle() == arr); + static_assert(mds1.extents() == extents{}); + + static_assert(!is_constructible_v, int*, + array>); // Empty not convertible to size_type + + static_assert(!is_constructible_v, int*, + array>); // Pathological::Extents not constructible from int + + static_assert(!is_constructible_v, Pathological::Layout>, int*, + array>); // Pathological::Layout not constructible from extents + + static_assert( + !is_constructible_v, layout_right, Pathological::Accessor>, int*, + array>); // Pathological::Accessor not default constructible +} + +void mdspan_tests_ctor_extents() { + static constexpr int arr[6] = {}; + constexpr mdspan> mds1(arr, extents{2}); + static_assert(mds1.data_handle() == arr); + static_assert(mds1.extents() == extents{}); + + static_assert(!is_constructible_v, Pathological::Layout>, int*, + extents>); // Pathological::Layout not constructible from extents + + static_assert( + !is_constructible_v, layout_right, Pathological::Accessor>, int*, + extents>); // Pathological::Accessor not default constructible +} + +void mdspan_tests_ctor_mapping() { + static constexpr int arr[6] = {}; + using E = extents; + constexpr layout_left::mapping> map(extents{}); + + constexpr mdspan mds1(arr, map); + static_assert(mds1.data_handle() == arr); + static_assert(mds1.extents() == extents{}); + static_assert(mds1.mapping() == map); + + static_assert( + !is_constructible_v, layout_right, Pathological::Accessor>, int*, + extents>); // Pathological::Accessor not default constructible +} + +template +struct stateful_accessor { + using data_handle_type = Type*; + using reference = Type&; + + constexpr stateful_accessor(int i_) : i(i_){}; + int i = 0; +}; + +void mdspan_tests_ctor_accessor() { + static constexpr int arr[6] = {}; + using E = extents; + constexpr layout_left::mapping> map(extents{}); + constexpr stateful_accessor acc(1); + + constexpr mdspan> mds1(arr, map, acc); + static_assert(mds1.data_handle() == arr); + static_assert(mds1.extents() == extents{}); + static_assert(mds1.mapping() == map); + static_assert(mds1.accessor().i == 1); + + static_assert( + !is_constructible_v, layout_right, Pathological::Accessor>, int*, + extents>); // Pathological::Accessor not default constructible +} + +void mdspan_tests_assign() { + using E2 = extents; + int arr[6] = {}; + mdspan> mds1(arr, 2); + mdspan> mds2(nullptr, 3); + mds2 = mds1; + assert(mds2.data_handle() == arr); + assert(mds2.extents() == E2{}); + assert(mds2.mapping() == mds1.mapping()); +} + +void mdspan_tests_observers() { + using E = extents; + static constexpr int arr[] = {0, 1, 2, 3, 4, 5, 6, 7}; + constexpr mdspan mds{arr, layout_stride::mapping{E{2}, array{1, 3}}}; + + static_assert(mds.rank() == 2); + static_assert(mds.rank_dynamic() == 1); + + static_assert(mds.static_extent(0) == dynamic_extent); + static_assert(mds.static_extent(1) == 3); + static_assert(mds.extent(0) == 2); + static_assert(mds.extent(1) == 3); + static_assert(mds.size() == 6); + + static_assert(mds.stride(0) == 1); + static_assert(mds.stride(1) == 3); + + static_assert(mds.is_always_unique()); + static_assert(!mds.is_always_exhaustive()); + static_assert(mds.is_always_strided()); + + static_assert(mds.is_unique()); + static_assert(!mds.is_exhaustive()); + static_assert(mds.is_strided()); + + static_assert(mds(1, 0) == 1); + static_assert(mds(1, 2) == 7); + + static_assert(mds[array{0, 1}] == 3); + static_assert(mds[array{1, 1}] == 4); +} + +int main() { + extent_tests_rank(); + extent_tests_static_extent(); + extent_tests_extent(); + extent_tests_ctor_other_sizes(); + extent_tests_copy_ctor_other(); + extent_tests_ctor_array(); + extent_tests_ctor_span(); + extent_tests_equality(); + + layout_left_tests_traits(); + layout_left_tests_properties(); + layout_left_tests_extents_ctor(); + layout_left_tests_copy_ctor(); + layout_left_tests_copy_other_extent(); + layout_left_tests_assign(); + layout_left_tests_ctor_other_layout(); + layout_left_tests_strides(); + layout_left_tests_indexing(); + + layout_right_tests_traits(); + layout_right_tests_properties(); + layout_right_tests_extents_ctor(); + layout_right_tests_copy_ctor(); + layout_right_tests_copy_ctor_other(); + layout_right_tests_assign(); + layout_right_tests_ctor_other_layout(); + layout_right_tests_strides(); + layout_right_tests_indexing(); + + layout_stride_tests_traits(); + layout_stride_tests_properties(); + layout_stride_tests_extents_ctor(); + layout_stride_tests_strides(); + layout_stride_tests_copy_ctor(); + layout_stride_tests_ctor_other_extents(); + layout_stride_tests_ctor_other_mapping(); + layout_stride_tests_assign(); + layout_stride_tests_indexing_static(); + layout_stride_tests_equality(); + + accessor_tests_general(); + + mdspan_tests_traits(); + mdspan_tests_ctor_sizes(); + mdspan_tests_ctor_array(); + mdspan_tests_ctor_extents(); + mdspan_tests_ctor_mapping(); + mdspan_tests_ctor_accessor(); + mdspan_tests_assign(); + mdspan_tests_observers(); + + return 0; +} \ No newline at end of file diff --git a/tests/std/tests/P1502R1_standard_library_header_units/importable_cxx_library_headers.jsonc b/tests/std/tests/P1502R1_standard_library_header_units/importable_cxx_library_headers.jsonc index ad8b6c8ab9e..65d0cc77e46 100644 --- a/tests/std/tests/P1502R1_standard_library_header_units/importable_cxx_library_headers.jsonc +++ b/tests/std/tests/P1502R1_standard_library_header_units/importable_cxx_library_headers.jsonc @@ -40,6 +40,7 @@ "list", "locale", "map", + "mdspan", "memory", "memory_resource", "mutex", diff --git a/tests/std/tests/P1502R1_standard_library_header_units/test.cpp b/tests/std/tests/P1502R1_standard_library_header_units/test.cpp index e9ec1a36cf0..cd9d33717e0 100644 --- a/tests/std/tests/P1502R1_standard_library_header_units/test.cpp +++ b/tests/std/tests/P1502R1_standard_library_header_units/test.cpp @@ -46,6 +46,7 @@ import ; import ; import ; import ; +import ; import ; import ; import ; diff --git a/tests/std/tests/VSO_0157762_feature_test_macros/test.compile.pass.cpp b/tests/std/tests/VSO_0157762_feature_test_macros/test.compile.pass.cpp index 7dedee3a9d1..1a08c884d5b 100644 --- a/tests/std/tests/VSO_0157762_feature_test_macros/test.compile.pass.cpp +++ b/tests/std/tests/VSO_0157762_feature_test_macros/test.compile.pass.cpp @@ -1322,6 +1322,20 @@ STATIC_ASSERT(__cpp_lib_math_special_functions == 201603L); #endif #endif +#if _HAS_CXX23 +#ifndef __cpp_lib_mdspan +#error __cpp_lib_mdspan is not defined +#elif __cpp_lib_mdspan != 202207L +#error __cpp_lib_mdspan is not 202207L +#else +STATIC_ASSERT(__cpp_lib_mdspan == 202207L); +#endif +#else +#ifdef __cpp_lib_mdspan +#error __cpp_lib_mdspan is defined +#endif +#endif + #if _HAS_CXX17 #ifndef __cpp_lib_memory_resource #error __cpp_lib_memory_resource is not defined From b1f089340facbccc0113822bbd16ad17428f3872 Mon Sep 17 00:00:00 2001 From: Matt Stephanson Date: Fri, 24 Feb 2023 20:29:19 -0800 Subject: [PATCH 2/3] clang-format --- stl/inc/mdspan | 10 ++++++---- tests/std/tests/P0009R18_mdspan/env.lst | 2 +- tests/std/tests/P0009R18_mdspan/test.cpp | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/stl/inc/mdspan b/stl/inc/mdspan index 34070dbb615..4724eff0d79 100644 --- a/stl/inc/mdspan +++ b/stl/inc/mdspan @@ -769,11 +769,13 @@ public: : _Ptr{_Ptr_}, _Map{_Map_}, _Acc{_Acc_} {} template &> - && is_constructible_v, + enable_if_t< + is_constructible_v&> + && is_constructible_v, int> = 0> - constexpr explicit(!is_convertible_v&, mapping_type> - || !is_convertible_v) + constexpr explicit( + !is_convertible_v&, mapping_type> + || !is_convertible_v) mdspan(const mdspan<_OtherElementType, _OtherExtents, _OtherLayoutPolicy, _OtherAccessor>& _Other) : _Ptr{_Other._Ptr}, _Map{_Other._Map}, _Acc{_Other._Acc} { static_assert(is_constructible_v); diff --git a/tests/std/tests/P0009R18_mdspan/env.lst b/tests/std/tests/P0009R18_mdspan/env.lst index 9c93b590ac7..642f530ffad 100644 --- a/tests/std/tests/P0009R18_mdspan/env.lst +++ b/tests/std/tests/P0009R18_mdspan/env.lst @@ -1,4 +1,4 @@ # Copyright (c) Microsoft Corporation. # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -RUNALL_INCLUDE ..\usual_latest_matrix.lst \ No newline at end of file +RUNALL_INCLUDE ..\usual_latest_matrix.lst diff --git a/tests/std/tests/P0009R18_mdspan/test.cpp b/tests/std/tests/P0009R18_mdspan/test.cpp index f528252183d..0a64ec9e37e 100644 --- a/tests/std/tests/P0009R18_mdspan/test.cpp +++ b/tests/std/tests/P0009R18_mdspan/test.cpp @@ -1030,7 +1030,7 @@ void mdspan_tests_assign() { } void mdspan_tests_observers() { - using E = extents; + using E = extents; static constexpr int arr[] = {0, 1, 2, 3, 4, 5, 6, 7}; constexpr mdspan mds{arr, layout_stride::mapping{E{2}, array{1, 3}}}; From f33b7001e2d13668cd6bed441b400f243f9b5570 Mon Sep 17 00:00:00 2001 From: Matt Stephanson Date: Fri, 24 Feb 2023 20:34:09 -0800 Subject: [PATCH 3/3] missing newline --- tests/std/tests/P0009R18_mdspan/test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/std/tests/P0009R18_mdspan/test.cpp b/tests/std/tests/P0009R18_mdspan/test.cpp index 0a64ec9e37e..1da04b34d52 100644 --- a/tests/std/tests/P0009R18_mdspan/test.cpp +++ b/tests/std/tests/P0009R18_mdspan/test.cpp @@ -1114,4 +1114,4 @@ int main() { mdspan_tests_observers(); return 0; -} \ No newline at end of file +}