diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt --- a/libcxx/include/CMakeLists.txt +++ b/libcxx/include/CMakeLists.txt @@ -439,6 +439,7 @@ __iterator/wrap_iter.h __locale __mbstate_t.h + __mdspan/extents.h __memory/addressof.h __memory/align.h __memory/aligned_alloc.h @@ -854,6 +855,7 @@ locale.h map math.h + mdspan memory memory_resource mutex diff --git a/libcxx/include/__mdspan/extents.h b/libcxx/include/__mdspan/extents.h new file mode 100644 --- /dev/null +++ b/libcxx/include/__mdspan/extents.h @@ -0,0 +1,476 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// Kokkos v. 4.0 +// Copyright (2022) National Technology & Engineering +// Solutions of Sandia, LLC (NTESS). +// +// Under the terms of Contract DE-NA0003525 with NTESS, +// the U.S. Government retains certain rights in this software. +// +//===---------------------------------------------------------------------===// + +#ifndef _LIBCPP_EXTENTS +#define _LIBCPP_EXTENTS + +#include +#include +#include +#include + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +#endif + +_LIBCPP_PUSH_MACROS +#include <__undef_macros> + +_LIBCPP_BEGIN_NAMESPACE_STD + +#if _LIBCPP_STD_VER > 20 + +namespace detail { + +// ------------------------------------------------------------------ +// ------------ static_array ---------------------------------------- +// ------------------------------------------------------------------ +// array like class which provides an array of static values with get + +// Implementation of Static Array with recursive implementation of get. +template +struct static_array_impl; + +template +struct static_array_impl { + constexpr static T get(size_t r) noexcept { + if (r == R) + return FirstExt; + else + return static_array_impl::get(r); + } + template + constexpr static T get() { + if constexpr (r == R) + return FirstExt; + else + return static_array_impl::template get(); + } +}; + +// End the recursion +template +struct static_array_impl { + constexpr static T get(size_t) noexcept { return FirstExt; } + template + constexpr static T get() { + return FirstExt; + } +}; + +// Don't start recursion if size 0 +template +struct static_array_impl<0, T> { + constexpr static T get(size_t) noexcept { return T(); } + template + constexpr static T get() { + return T(); + } +}; + +// Static array, provides get(), get(r) and operator[r] +template +struct static_array : public static_array_impl<0, T, Values...> { +public: + using value_type = T; + + constexpr static size_t size() { return sizeof...(Values); } +}; + +// ------------------------------------------------------------------ +// ------------ index_sequence_scan --------------------------------- +// ------------------------------------------------------------------ + +// index_sequence_scan takes compile time values and provides get(r) +// which return the sum of the first r-1 values. + +// Recursive implementation for get +template +struct index_sequence_scan_impl; + +template +struct index_sequence_scan_impl { + constexpr static size_t get(size_t r) { + if (r > R) + return FirstVal + index_sequence_scan_impl::get(r); + else + return 0; + } +}; + +template +struct index_sequence_scan_impl { +# if defined(__NVCC__) || defined(__NVCOMPILER) + // NVCC warns about pointless comparison with 0 for R==0 and r being const + // evaluatable and also 0. + constexpr static size_t get(size_t r) { return static_cast(R) > static_cast(r) ? FirstVal : 0; } +# else + constexpr static size_t get(size_t r) { return R > r ? FirstVal : 0; } +# endif +}; +template <> +struct index_sequence_scan_impl<0> { + constexpr static size_t get(size_t) { return 0; } +}; + +// ------------------------------------------------------------------ +// ------------ possibly_empty_array ------------------------------- +// ------------------------------------------------------------------ + +// array like class which provides get function and operator [], and +// has a specialization for the size 0 case. +// This is needed to make the maybe_static_array be truly empty, for +// all static values. + +template +struct possibly_empty_array { + T vals[N]; + constexpr T& operator[](size_t r) { return vals[r]; } + constexpr const T& operator[](size_t r) const { return vals[r]; } +}; + +template +struct possibly_empty_array { + constexpr T operator[](size_t) { return T(); } + constexpr const T operator[](size_t) const { return T(); } +}; + +// ------------------------------------------------------------------ +// ------------ maybe_static_array ---------------------------------- +// ------------------------------------------------------------------ + +// array like class which has a mix of static and runtime values but +// only stores the runtime values. +// The type of the static and the runtime values can be different. +// The position of a dynamic value is indicated through a tag value. +template +struct maybe_static_array { + static_assert(is_convertible::value, + "maybe_static_array: TStatic must be convertible to TDynamic"); + static_assert(is_convertible::value, + "maybe_static_array: TDynamic must be convertible to TStatic"); + +private: + // Static values member + using static_vals_t = static_array; + constexpr static size_t m_size = sizeof...(Values); + constexpr static size_t m_size_dynamic = ((Values == dyn_tag) + ... + 0); + + // Dynamic values member + [[no_unique_address]] possibly_empty_array m_dyn_vals; + + // static mapping of indices to the position in the dynamic values array + using dyn_map_t = index_sequence_scan_impl<0, static_cast(Values == dyn_tag)...>; + +public: + // two types for static and dynamic values + using value_type = TDynamic; + using static_value_type = TStatic; + // tag value indicating dynamic value + constexpr static static_value_type tag_value = dyn_tag; + + constexpr maybe_static_array() = default; + + // constructor for all static values + // TODO: add precondition check? + template + requires((m_size_dynamic == 0) && (sizeof...(Vals) > 0)) + constexpr maybe_static_array(Vals...) : m_dyn_vals{} {} + + // constructors from dynamic values only + template + requires(sizeof...(DynVals) == m_size_dynamic && m_size_dynamic > 0) + constexpr maybe_static_array(DynVals... vals) : m_dyn_vals{static_cast(vals)...} {} + + template + requires(N == m_size_dynamic && N > 0) + constexpr maybe_static_array(const std::array& vals) { + for (size_t r = 0; r < N; r++) + m_dyn_vals[r] = static_cast(vals[r]); + } + + template + requires(N == m_size_dynamic && N == 0) + constexpr maybe_static_array(const std::array&) : m_dyn_vals{} {} + + template + requires(N == m_size_dynamic && N > 0) + constexpr maybe_static_array(const std::span& vals) { + for (size_t r = 0; r < N; r++) + m_dyn_vals[r] = static_cast(vals[r]); + } + + template + requires(N == m_size_dynamic && N == 0) + constexpr maybe_static_array(const std::span&) : m_dyn_vals{} {} + + // constructors from all values + template + requires(sizeof...(DynVals) != m_size_dynamic && m_size_dynamic > 0) + constexpr maybe_static_array(DynVals... vals) { + static_assert((sizeof...(DynVals) == m_size), "Invalid number of values."); + TDynamic values[m_size]{static_cast(vals)...}; + for (size_t r = 0; r < m_size; r++) { + TStatic static_val = static_vals_t::get(r); + if (static_val == dyn_tag) { + m_dyn_vals[dyn_map_t::get(r)] = values[r]; + } +// Precondition check +# ifdef _MDSPAN_DEBUG + else { + assert(values[r] == static_cast(static_val)); + } +# endif + } + } + + template + requires(N != m_size_dynamic && m_size_dynamic > 0) + constexpr maybe_static_array(const std::array& vals) { + static_assert((N == m_size), "Invalid number of values."); +// Precondition check +# ifdef _MDSPAN_DEBUG + assert(N == m_size); +# endif + for (size_t r = 0; r < m_size; r++) { + TStatic static_val = static_vals_t::get(r); + if (static_val == dyn_tag) { + m_dyn_vals[dyn_map_t::get(r)] = static_cast(vals[r]); + } +// Precondition check +# ifdef _MDSPAN_DEBUG + else { + assert(static_cast(vals[r]) == static_cast(static_val)); + } +# endif + } + } + + template + requires(N != m_size_dynamic && m_size_dynamic > 0) + constexpr maybe_static_array(const std::span& vals) { + static_assert((N == m_size) || (m_size == dynamic_extent)); +# ifdef _MDSPAN_DEBUG + assert(N == m_size); +# endif + for (size_t r = 0; r < m_size; r++) { + TStatic static_val = static_vals_t::get(r); + if (static_val == dyn_tag) { + m_dyn_vals[dyn_map_t::get(r)] = static_cast(vals[r]); + } +# ifdef _MDSPAN_DEBUG + else { + assert(static_cast(vals[r]) == static_cast(static_val)); + } +# endif + } + } + + // access functions + constexpr static TStatic static_value(size_t r) noexcept { return static_vals_t::get(r); } + + constexpr TDynamic value(size_t r) const { + TStatic static_val = static_vals_t::get(r); + return static_val == dyn_tag ? m_dyn_vals[dyn_map_t::get(r)] : static_cast(static_val); + } + constexpr TDynamic operator[](size_t r) const { return value(r); } + + // observers + constexpr static size_t size() { return m_size; } + constexpr static size_t size_dynamic() { return m_size_dynamic; } +}; + +} // namespace detail + +// ------------------------------------------------------------------ +// ------------ extents --------------------------------------------- +// ------------------------------------------------------------------ + +// Class to describe the extents of a multi dimensional array. +// Used by mdspan, mdarray and layout mappings. +// See ISO C++ standard [mdspan.extents] + +template +class extents { +public: + // typedefs for integral types used + using index_type = IndexType; + using size_type = make_unsigned_t; + using rank_type = size_t; + + static_assert(std::is_integral::value && !std::is_same::value, + "extents::index_type must be a signed or unsigned integer type"); + +private: + constexpr static rank_type m_rank = sizeof...(Extents); + constexpr static rank_type m_rank_dynamic = ((Extents == dynamic_extent) + ... + 0); + + // internal storage type using maybe_static_array + using vals_t = detail::maybe_static_array; + [[no_unique_address]] vals_t m_vals; + +public: + // [mdspan.extents.obs], observers of multidimensional index space + constexpr static rank_type rank() noexcept { return m_rank; } + constexpr static rank_type rank_dynamic() noexcept { return m_rank_dynamic; } + + constexpr index_type extent(rank_type r) const noexcept { return m_vals.value(r); } + constexpr static size_t static_extent(rank_type r) noexcept { return vals_t::static_value(r); } + + // [mdspan.extents.cons], constructors + constexpr extents() noexcept = default; + + // Construction from just dynamic or all values. + // Precondition check is deferred to maybe_static_array constructor + template + requires((is_convertible_v && ...) && + (is_nothrow_constructible_v && ...) && + (sizeof...(OtherIndexTypes) == m_rank || sizeof...(OtherIndexTypes) == m_rank_dynamic)) + constexpr explicit extents(OtherIndexTypes... dynvals) noexcept : m_vals(static_cast(dynvals)...) {} + + template + requires(is_convertible_v&& is_nothrow_constructible_v && + (N == m_rank || N == m_rank_dynamic)) + explicit(N != m_rank_dynamic) constexpr extents(const array& exts) noexcept + : m_vals(std::move(exts)) {} + + template + requires(is_convertible_v&& is_nothrow_constructible_v && + (N == m_rank || N == m_rank_dynamic)) + explicit(N != m_rank_dynamic) constexpr extents(const span& exts) noexcept + : m_vals(std::move(exts)) {} + +private: + // Function to construct extents storage from other extents. + // With C++ 17 the first two variants could be collapsed using if constexpr + // in which case you don't need all the requires clauses. + // in C++ 14 mode that doesn't work due to infinite recursion + template + requires((R < m_rank) && (static_extent(R) == dynamic_extent)) + vals_t __construct_vals_from_extents( + std::integral_constant, + std::integral_constant, + const OtherExtents& exts, + DynamicValues... dynamic_values) + noexcept { + return __construct_vals_from_extents( + std::integral_constant(), + std::integral_constant(), + exts, + dynamic_values..., + exts.extent(R)); + } + + template + requires((R < m_rank) && (static_extent(R) != dynamic_extent)) + vals_t __construct_vals_from_extents( + std::integral_constant, + std::integral_constant, + const OtherExtents& exts, + DynamicValues... dynamic_values) + noexcept { + return __construct_vals_from_extents( + std::integral_constant(), std::integral_constant(), exts, dynamic_values...); + } + + template + requires((R == m_rank) && (DynCount == m_rank_dynamic)) + vals_t __construct_vals_from_extents( + std::integral_constant, + std::integral_constant, + const OtherExtents&, + DynamicValues... dynamic_values) + noexcept { return vals_t{static_cast(dynamic_values)...}; } + +public: + // Converting constructor from other extents specializations + template + requires((sizeof...(OtherExtents) == sizeof...(Extents)) && + ((OtherExtents == dynamic_extent || Extents == dynamic_extent || OtherExtents == Extents) && ...)) + explicit( + (((Extents != dynamic_extent) && (OtherExtents == dynamic_extent)) || ...) || + (std::numeric_limits::max() < + std::numeric_limits::max())) constexpr extents(const extents& + other) noexcept + : m_vals(__construct_vals_from_extents( + std::integral_constant(), std::integral_constant(), other)) {} + + // Comparison operator + template + friend constexpr bool operator==(const extents& lhs, const extents& rhs) noexcept { + bool value = true; + if constexpr (rank() != sizeof...(OtherExtents)) { + value = false; + } else { + for (size_type r = 0; r < m_rank; r++) + value &= static_cast(rhs.extent(r)) == lhs.extent(r); + } + return value; + } + + template + friend constexpr bool operator!=(extents const& lhs, extents const& rhs) noexcept { + return !(lhs == rhs); + } +}; + +// Recursive helper classes to implement dextents alias for extents +namespace detail { + +template > +struct __make_dextents; + +template +struct __make_dextents< IndexType, Rank, extents> { + using type = typename __make_dextents< IndexType, Rank - 1, extents>::type; +}; + +template +struct __make_dextents< IndexType, 0, extents> { + using type = extents; +}; + +} // end namespace detail + +// [mdspan.extents.dextents], alias template +template +using dextents = typename detail::__make_dextents::type; + +// Deduction guide for extents +template +extents(IndexTypes...) -> extents; + +// Helper type traits for identifying a class as extents. +namespace detail { + +template +struct __is_extents : ::std::false_type {}; + +template +struct __is_extents> : ::std::true_type {}; + +template +inline constexpr bool __is_extents_v = __is_extents::value; + +} // namespace detail + +#endif // _LIBCPP_STD_VER > 20 + +_LIBCPP_END_NAMESPACE_STD + +_LIBCPP_POP_MACROS + +#endif // _LIBCPP_EXTENTS diff --git a/libcxx/include/mdspan b/libcxx/include/mdspan new file mode 100644 --- /dev/null +++ b/libcxx/include/mdspan @@ -0,0 +1,29 @@ +// -*-C++ - *- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===---------------------------------------------------------------------===// + +#ifndef _LIBCPP_MDSPAN +#define _LIBCPP_MDSPAN + +#include <__config> +#include <__mdspan/extents.h> + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +#endif + +_LIBCPP_PUSH_MACROS +#include <__undef_macros> + +_LIBCPP_BEGIN_NAMESPACE_STD + +_LIBCPP_END_NAMESPACE_STD + +_LIBCPP_POP_MACROS + +#endif // _LIBCPP_MDSPAN diff --git a/libcxx/include/module.modulemap.in b/libcxx/include/module.modulemap.in --- a/libcxx/include/module.modulemap.in +++ b/libcxx/include/module.modulemap.in @@ -1119,6 +1119,16 @@ export initializer_list export * } + module mdspan { + header "mdspan" + export array + export span + export * + + module __mdspan { + module extents { private header "__mdspan/extents.h" } + } + } module memory { header "memory" export * diff --git a/libcxx/test/std/containers/views/mdspan/extents/ConvertibleToIntegral.h b/libcxx/test/std/containers/views/mdspan/extents/ConvertibleToIntegral.h new file mode 100644 --- /dev/null +++ b/libcxx/test/std/containers/views/mdspan/extents/ConvertibleToIntegral.h @@ -0,0 +1,22 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CONVERTIBLE_TO_INTEGRAL_H +#define CONVERTIBLE_TO_INTEGRAL_H + +struct IntType { + int val; + constexpr IntType() = default; + constexpr IntType(int v) noexcept : val(v){}; + + constexpr bool operator==(const IntType& rhs) const { return val == rhs.val; } + constexpr operator int() const noexcept { return val; } + constexpr operator unsigned char() const noexcept { return val; } +}; + +#endif diff --git a/libcxx/test/std/containers/views/mdspan/extents/comparison.pass.cpp b/libcxx/test/std/containers/views/mdspan/extents/comparison.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/containers/views/mdspan/extents/comparison.pass.cpp @@ -0,0 +1,68 @@ +#include +#include +#include +#include + +#include "test_macros.h" + +template +void test_comparison(bool equal, To dest, From src) { + assert((dest == src) == equal); + assert((dest != src) != equal); +} + +template +void test_comparison_different_rank() { + constexpr size_t D = std::dynamic_extent; + + test_comparison(false, std::extents(), std::extents(1)); + test_comparison(false, std::extents(), std::extents()); + + test_comparison(false, std::extents(1), std::extents()); + test_comparison(false, std::extents(), std::extents()); + + test_comparison(false, std::extents(5), std::extents(5, 5)); + test_comparison(false, std::extents(), std::extents(5)); + test_comparison(false, std::extents(), std::extents()); + + test_comparison(false, std::extents(5, 5), std::extents(5)); + test_comparison(false, std::extents(5), std::extents(5)); + test_comparison(false, std::extents(), std::extents()); +} + +template +void test_comparison_same_rank() { + constexpr size_t D = std::dynamic_extent; + + test_comparison(true, std::extents(), std::extents()); + + test_comparison(true, std::extents(5), std::extents(5)); + test_comparison(true, std::extents(), std::extents(5)); + test_comparison(true, std::extents(5), std::extents()); + test_comparison(true, std::extents(), std::extents< T2, 5>()); + test_comparison(false, std::extents(5), std::extents(7)); + test_comparison(false, std::extents(), std::extents(7)); + test_comparison(false, std::extents(5), std::extents()); + test_comparison(false, std::extents(), std::extents()); + + test_comparison(true, std::extents(5, 6, 7, 8, 9), std::extents(5, 6, 7, 8, 9)); + test_comparison(true, std::extents(5, 7, 9), std::extents(6, 7)); + test_comparison(true, std::extents(5, 6, 7, 8, 9), std::extents()); + test_comparison( + false, std::extents(5, 6, 7, 8, 9), std::extents(5, 6, 3, 8, 9)); + test_comparison(false, std::extents(5, 7, 9), std::extents(6, 7)); + test_comparison(false, std::extents(5, 6, 7, 8, 9), std::extents()); +} + +template +void test_comparison() { + test_comparison_same_rank(); + test_comparison_different_rank(); +} + +int main() { + test_comparison(); + test_comparison(); + test_comparison(); + test_comparison(); +} diff --git a/libcxx/test/std/containers/views/mdspan/extents/cons.fail.cpp b/libcxx/test/std/containers/views/mdspan/extents/cons.fail.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/containers/views/mdspan/extents/cons.fail.cpp @@ -0,0 +1,77 @@ +#include +#include +#include +#include + +#include "ConvertibleToIntegral.h" +#include "test_macros.h" + +template +void implicit_construction(E) {} + +template +void test() { + constexpr size_t D = std::dynamic_extent; + + // clang-format off + // test less than rank_dynammic args + { + std::array a1{3}; + std::span s1(a1.data(), 1); + std::extents e1(2); // expected-error {{no matching constructor for initialization of 'std::extents'}} + std::extents e2(a1); // expected-error {{no matching constructor for initialization of 'std::extents'}} + std::extents e3(s1); // expected-error {{no matching constructor for initialization of 'std::extents'}} + (void)e1; + (void)e2; + (void)e3; + } + // test more than rank_dynammic but less than rank args + { + std::array a3{3, 4, 5}; + std::span s3(a3.data(), 3); + std::extents e1(3, 4, 5); // expected-error {{no matching constructor for initialization of 'std::extents'}} + std::extents e2(a3); // expected-error {{no matching constructor for initialization of 'std::extents'}} + std::extents e3(s3); // expected-error {{no matching constructor for initialization of 'std::extents'}} + (void)e1; + (void)e2; + (void)e3; + } + // test more than rank args + { + std::array a5{3, 4, 5, 6, 7}; + std::span s5(a5.data(), 5); + std::extents e1(3, 4, 5, 6, 7); // expected-error {{no matching constructor for initialization of 'std::extents'}} + std::extents e2(a5); // expected-error {{no matching constructor for initialization of 'std::extents'}} + std::extents e3(s5); // expected-error {{no matching constructor for initialization of 'std::extents'}} + (void)e1; + (void)e2; + (void)e3; + } + + // test implicit construction fails from span and array if all extents are given + { + std::array a5{3, 4, 5, 6, 7}; + std::span s5(a5.data(), 5); + // check that explicit construction works, i.e. no error + std::extents e1(a5); + std::extents e2(s5); + (void)e1; + (void)e2; + implicit_construction>(a5); // expected-error {{no matching function for call to 'implicit_construction'}} + implicit_construction>(s5); // expected-error {{no matching function for call to 'implicit_construction'}} + } + // test construction fails from types not convertible to index_type but convertible to other integer types + { + std::array a{IntType(3)}; + std::span s{a}; + std::extents e1(IntType(3)); // expected-error {{no matching constructor for initialization of 'std::extents'}} + std::extents e2(a); // expected-error {{no matching constructor for initialization of 'std::extents'}} + std::extents e3(s); // expected-error {{no matching constructor for initialization of 'std::extents'}} + (void)e1; + (void)e2; + (void)e3; + } + // clang-format on +} + +int main() { test(); } diff --git a/libcxx/test/std/containers/views/mdspan/extents/cons.pass.cpp b/libcxx/test/std/containers/views/mdspan/extents/cons.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/containers/views/mdspan/extents/cons.pass.cpp @@ -0,0 +1,102 @@ +#include +#include +#include +#include + +#include "ConvertibleToIntegral.h" +#include "test_macros.h" + +// std::extents can be constructed from just indices, a std::array, or a std::span +// In each of those cases one can either provide all extents, or just the dynamic ones +// If constructed from std::span, the span needs to have a static extent +// Furthermore, the indices/array/span can have integer types other than index_type + +template +void test_runtime_observers(E ext, AllExtents expected) { + for (typename E::rank_type r = 0; r < ext.rank(); r++) { + ASSERT_SAME_TYPE(decltype(ext.extent(0)), typename E::index_type); + assert(ext.extent(r) == static_cast(expected[r])); + } +} + +template +void test_implicit_construction_call(E e, AllExtents all_ext) { + test_runtime_observers(e, all_ext); +} + +template +void test_implicit_construction(AllExtents all_ext, Extents ext) { + test_implicit_construction_call(ext, all_ext); + test_implicit_construction_call( + std::span(ext.data(), ext.size()), all_ext); +} + +template +void test_construction(AllExtents all_ext, Extents ext, std::index_sequence) { + // construction from indices + test_runtime_observers(E(ext[Indices]...), all_ext); + // construction from array + test_runtime_observers(E(ext), all_ext); + // construction from span + test_runtime_observers( + E(std::span(ext.data(), sizeof...(Indices))), all_ext); +} + +template +void test_construction(AllExtents all_ext) { + // test construction from all extents + test_construction(all_ext, all_ext, std::make_index_sequence()); + + // test construction from just dynamic extents + // create an array of just the extents corresponding to dynamic values + std::array dyn_ext; + size_t dynamic_idx = 0; + for (size_t r = 0; r < E::rank(); r++) { + if (E::static_extent(r) == std::dynamic_extent) { + dyn_ext[dynamic_idx] = all_ext[r]; + dynamic_idx++; + } + } + test_construction(all_ext, dyn_ext, std::make_index_sequence()); + test_implicit_construction(all_ext, dyn_ext); +} + +template +void test() { + constexpr size_t D = std::dynamic_extent; + + test_construction>(std::array{}); + + test_construction>(std::array{3}); + test_construction>(std::array{3}); + + test_construction>(std::array{3, 7}); + test_construction>(std::array{3, 7}); + test_construction>(std::array{3, 7}); + test_construction>(std::array{3, 7}); + + test_construction>(std::array{3, 7, 9}); + test_construction>(std::array{3, 7, 9}); + test_construction>(std::array{3, 7, 9}); + test_construction>(std::array{3, 7, 9}); + test_construction>(std::array{3, 7, 9}); + test_construction>(std::array{3, 7, 9}); + test_construction>(std::array{3, 7, 9}); + test_construction>(std::array{3, 7, 9}); + + test_construction>(std::array{1, 2, 3, 4, 5, 6, 7, 8, 9}); + test_construction>(std::array{1, 2, 3, 4, 5, 6, 7, 8, 9}); + test_construction>(std::array{1, 2, 3, 4, 5, 6, 7, 8, 9}); +} + +int main() { + test(); + test(); + test(); + test(); + test(); + test(); + test(); + test(); + test(); +} diff --git a/libcxx/test/std/containers/views/mdspan/extents/constexpr_usage.pass.cpp b/libcxx/test/std/containers/views/mdspan/extents/constexpr_usage.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/containers/views/mdspan/extents/constexpr_usage.pass.cpp @@ -0,0 +1,124 @@ +#include +#include +#include +#include + +#include "ConvertibleToIntegral.h" +#include "test_macros.h" + +// std::extents can be constructed from just indices, a std::array, or a std::span +// In each of those cases one can either provide all extents, or just the dynamic ones +// If constructed from std::span, the span needs to have a static extent +// Furthermore, the indices/array/span can have integer types other than index_type + +// All of this should work in constant expressions + +// This tests the actual observers and returns the sum of extents + rank +template +constexpr size_t test_runtime_observers(E ext, AllExtents expected) { + size_t result = 0; + for (typename E::rank_type r = 0; r < ext.rank(); r++) { + result += ext.extent(r) == static_cast(expected[r]); + result += ext.extent(r); + } + return result; +} + +// Make sure constexpr usage of extents works in implicit construction +template +constexpr size_t test_implicit_construction_call(E e, AllExtents all_ext) { + return test_runtime_observers(e, all_ext); +} + +template +constexpr size_t test_implicit_construction(AllExtents all_ext, Extents ext) { + return test_implicit_construction_call(ext, all_ext) + + test_implicit_construction_call( + std::span(ext.data(), ext.size()), all_ext); +} + +template +constexpr size_t test_construction(AllExtents all_ext, Extents ext, std::index_sequence) { + size_t result = 0; + // construction from indicies + result += test_runtime_observers(E(ext[Indices]...), all_ext); + // construction from array + result += test_runtime_observers(E(ext), all_ext); + // construction from span + result += test_runtime_observers( + E(std::span(ext.data(), sizeof...(Indices))), all_ext); + return result; +} + +template +constexpr size_t test_construction(AllExtents all_ext) { + size_t result = 0; + // test construction from all extents + result += test_construction(all_ext, all_ext, std::make_index_sequence()); + + // test construction from just dynamic extents + // create an array of just the extents corresponding to dynamic values + std::array dyn_ext{0}; + size_t dynamic_idx = 0; + for (size_t r = 0; r < E::rank(); r++) { + if (E::static_extent(r) == std::dynamic_extent) { + dyn_ext[dynamic_idx] = all_ext[r]; + dynamic_idx++; + } + } + result += test_construction(all_ext, dyn_ext, std::make_index_sequence()); + result += test_implicit_construction(all_ext, dyn_ext); + return result; +} + +template +struct const_expr { + // num_tests is how many different construction scenarios each + // construction tests expands to in the end. + // expected is the value each individual test return + constexpr static size_t num_tests = 8; + constexpr static bool value = result == num_tests * expected; +}; + +template +void test() { + constexpr size_t D = std::dynamic_extent; + + static_assert(const_expr>(std::array{}), 0>::value); + + static_assert(const_expr>(std::array{3}), 3 + 1>::value); + + static_assert(const_expr>(std::array{3}), 3 + 1>::value); + + static_assert(const_expr>(std::array{3, 7}), 10 + 2>::value); + static_assert(const_expr>(std::array{3, 7}), 10 + 2>::value); + static_assert(const_expr>(std::array{3, 7}), 10 + 2>::value); + static_assert(const_expr>(std::array{3, 7}), 10 + 2>::value); + + static_assert(const_expr>(std::array{3, 7, 9}), 19 + 3>::value); + static_assert(const_expr>(std::array{3, 7, 9}), 19 + 3>::value); + static_assert(const_expr>(std::array{3, 7, 9}), 19 + 3>::value); + static_assert(const_expr>(std::array{3, 7, 9}), 19 + 3>::value); + static_assert(const_expr>(std::array{3, 7, 9}), 19 + 3>::value); + static_assert(const_expr>(std::array{3, 7, 9}), 19 + 3>::value); + static_assert(const_expr>(std::array{3, 7, 9}), 19 + 3>::value); + static_assert(const_expr>(std::array{3, 7, 9}), 19 + 3>::value); + + static_assert(const_expr>( + std::array{1, 2, 3, 4, 5, 6, 7, 8, 9}), + 45 + 9>::value); + static_assert(const_expr>( + std::array{1, 2, 3, 4, 5, 6, 7, 8, 9}), + 45 + 9>::value); + static_assert(const_expr>( + std::array{1, 2, 3, 4, 5, 6, 7, 8, 9}), + 45 + 9>::value); +} + +int main() { + test(); + test(); + test(); + test(); + test(); +} diff --git a/libcxx/test/std/containers/views/mdspan/extents/conversion.fail.cpp b/libcxx/test/std/containers/views/mdspan/extents/conversion.fail.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/containers/views/mdspan/extents/conversion.fail.cpp @@ -0,0 +1,81 @@ +#include +#include +#include +#include + +#include "test_macros.h" + +// std::extents can be converted into each other as long as there aren't any +// mismatched static extents. +// Convertibility requires that no runtime dimension is assigned to a static dimension, +// and that the destinations index_type has a larger or equal max value than the +// sources index_type + +// clang-format off +void test_no_implicit_conversion() { + constexpr size_t D = std::dynamic_extent; + { + std::extents from; + std::extents to; + to = from; // expected-error {{no viable overloaded '='}} + } + { + std::extents from; + std::extents to; + to = from; // expected-error {{no viable overloaded '='}} + } + { + std::extents from; + std::extents to; + to = from; // expected-error {{no viable overloaded '='}} + } + { + std::extents from; + std::extents to; + to = from; // expected-error {{no viable overloaded '='}} + } +} + +void test_rank_mismatch() { + constexpr size_t D = std::dynamic_extent; + { + std::extents from; + std::extents to(from); // expected-error {{no matching constructor for initialization of 'std::extents'}} + (void) to; + } + { + std::extents from; + std::extents to0(from); // expected-error {{no matching constructor for initialization of 'std::extents'}} + std::extents to1(from); // expected-error {{no matching constructor for initialization of 'std::extents'}} + std::extents to3(from); // expected-error {{no matching constructor for initialization of 'std::extents'}} + (void) to0; + (void) to1; + (void) to3; + } +} + +void test_static_extent_mismatch() { + constexpr size_t D = std::dynamic_extent; + { + std::extents from; + std::extents to(from); // expected-error {{no matching constructor for initialization of 'std::extents'}} + (void) to; + } + { + std::extents from; + std::extents to(from); // expected-error {{no matching constructor for initialization of 'std::extents'}} + (void) to; + } + { + std::extents from; + std::extents to(from); // expected-error {{no matching constructor for initialization of 'std::extents'}} + (void) to; + } +} +// clang-format on + +int main() { + test_rank_mismatch(); + test_static_extent_mismatch(); + test_no_implicit_conversion(); +} diff --git a/libcxx/test/std/containers/views/mdspan/extents/conversion.pass.cpp b/libcxx/test/std/containers/views/mdspan/extents/conversion.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/containers/views/mdspan/extents/conversion.pass.cpp @@ -0,0 +1,57 @@ +#include +#include +#include +#include + +#include "test_macros.h" + +// std::extents can be converted into each other as long as there aren't any +// mismatched static extents. +// Convertibility requires that no runtime dimension is assigned to a static dimension, +// and that the destinations index_type has a larger or equal max value than the +// sources index_type + +template +void test_implicit_conversion(To dest, From src) { + assert(dest == src); +} + +template +void test_conversion(From src) { + To dest(src); + assert(dest == src); + if constexpr (implicit) { + dest = src; + assert(dest == src); + test_implicit_conversion(src, src); + } +} + +template +void test_conversion() { + constexpr size_t D = std::dynamic_extent; + constexpr bool idx_convertible = std::numeric_limits::max() >= std::numeric_limits::max(); + + // clang-format off + test_conversion>(std::extents()); + test_conversion>(std::extents(5)); + test_conversion>(std::extents(5)); + test_conversion>(std::extents()); + test_conversion>(std::extents(5, 5)); + test_conversion>(std::extents(5, 5)); + test_conversion>(std::extents(5)); + test_conversion>(std::extents()); + test_conversion>(std::extents(5, 7)); + test_conversion>( + std::extents(5, 7, 8, 9, 1)); + test_conversion>(std::extents(5)); + test_conversion>(std::extents()); + // clang-format on +} + +int main() { + test_conversion(); + test_conversion(); + test_conversion(); + test_conversion(); +} diff --git a/libcxx/test/std/containers/views/mdspan/extents/obs_static.pass.cpp b/libcxx/test/std/containers/views/mdspan/extents/obs_static.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/containers/views/mdspan/extents/obs_static.pass.cpp @@ -0,0 +1,60 @@ +#include +#include +#include +#include + +#include "test_macros.h" + +template +void test_static_observers(std::index_sequence, std::index_sequence) { + ASSERT_NOEXCEPT(E::rank()); + static_assert(E::rank() == rank); + ASSERT_NOEXCEPT(E::rank_dynamic()); + static_assert(E::rank_dynamic() == rank_dynamic); + + // Let's only test this if the call isn't a precondition violation + if constexpr (rank > 0) { + ASSERT_NOEXCEPT(E::static_extent(0)); + ASSERT_SAME_TYPE(decltype(E::static_extent(0)), size_t); + static_assert(((E::static_extent(Indices) == StaticExts) && ...)); + } +} + +template +void test_static_observers() { + test_static_observers( + std::index_sequence(), std::make_index_sequence()); +} + +template +void test() { + constexpr size_t D = std::dynamic_extent; + constexpr size_t S = 5; + + test_static_observers, 0, 0>(); + + test_static_observers, 1, 0, S>(); + test_static_observers, 1, 1, D>(); + + test_static_observers, 2, 0, S, S>(); + test_static_observers, 2, 1, S, D>(); + test_static_observers, 2, 1, D, S>(); + test_static_observers, 2, 2, D, D>(); + + test_static_observers, 3, 0, S, S, S>(); + test_static_observers, 3, 1, S, S, D>(); + test_static_observers, 3, 1, S, D, S>(); + test_static_observers, 3, 1, D, S, S>(); + test_static_observers, 3, 2, S, D, D>(); + test_static_observers, 3, 2, D, S, D>(); + test_static_observers, 3, 2, D, D, S>(); + test_static_observers, 3, 3, D, D, D>(); +} + +int main() { + test(); + test(); + test(); + test(); + test(); +} diff --git a/libcxx/test/std/containers/views/mdspan/extents/types.pass.cpp b/libcxx/test/std/containers/views/mdspan/extents/types.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/containers/views/mdspan/extents/types.pass.cpp @@ -0,0 +1,55 @@ +#include +#include +#include +#include + +#include "test_macros.h" + +template +void testExtents() { + ASSERT_SAME_TYPE(typename E::index_type, IndexType); + ASSERT_SAME_TYPE(typename E::size_type, std::make_unsigned_t); + ASSERT_SAME_TYPE(typename E::rank_type, size_t); + + static_assert(sizeof...(Extents) == E::rank()); + static_assert((static_cast(Extents == std::dynamic_extent) + ...) == E::rank_dynamic()); + + static_assert(std::regular); + static_assert(std::is_trivially_copyable_v); +} + +template +void testExtents() { + testExtents, IndexType, Extents...>(); +} + +template +void test() { + constexpr size_t D = std::dynamic_extent; + testExtents(); + testExtents(); + testExtents(); + testExtents(); + testExtents(); + testExtents(); + testExtents(); + testExtents(); + testExtents(); + testExtents(); + testExtents(); + testExtents(); + testExtents(); + testExtents(); + + testExtents(); + testExtents(); + testExtents(); +} + +int main() { + test(); + test(); + test(); + test(); + test(); +}