Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Host/Device accessors for mdspan #3686

Draft
wants to merge 26 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
b115039
first draft
fbusato Feb 5, 2025
0ea452d
split implemenation
fbusato Feb 5, 2025
9ae4953
add host/device pointer checks
fbusato Feb 5, 2025
96f8419
add pointer checks
fbusato Feb 6, 2025
52b12a6
add tests
fbusato Feb 6, 2025
cab748e
fix device/host memory accessibility detection
fbusato Feb 6, 2025
31b84c7
fix typo
fbusato Feb 6, 2025
430e46d
add recursive traits
fbusato Feb 6, 2025
3c76d32
prevent ADL
fbusato Feb 6, 2025
d63980d
size_t namespace
fbusato Feb 6, 2025
e9ce8a4
is_pointer namespace
fbusato Feb 6, 2025
de4264c
fix __self
fbusato Feb 7, 2025
c0d12da
add noexcept
fbusato Feb 7, 2025
572447d
Update libcudacxx/include/cuda/__mdspan/host_device_accessor.h
fbusato Feb 8, 2025
0eb90ef
Update libcudacxx/include/cuda/__mdspan/host_device_accessor.h
fbusato Feb 8, 2025
71a9161
expose accessor types
fbusato Feb 8, 2025
921273a
remove default, copy constructors
fbusato Feb 8, 2025
aef9602
fix default and copy constructors
fbusato Feb 10, 2025
0647703
add inheritance aliases
fbusato Feb 10, 2025
c30482e
Merge branch 'main' into host-device-mdspan-accessors
fbusato Feb 10, 2025
fc82009
add default_accessor constructor and conversion
fbusato Feb 10, 2025
21e2feb
guard cuda_runtime_api.h
fbusato Feb 14, 2025
22fecf3
Merge branch 'main' into host-device-mdspan-accessors
fbusato Feb 14, 2025
ca47b44
Update libcudacxx/include/cuda/__mdspan/host_device_accessor.h
fbusato Feb 20, 2025
9a7e393
add detectably_invalid
fbusato Feb 20, 2025
a0af8f4
add __managed_accessor conversions from/to default_accessors
fbusato Feb 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
328 changes: 328 additions & 0 deletions libcudacxx/include/cuda/__mdspan/host_device_accessor.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,328 @@
//===----------------------------------------------------------------------===//
//
// Part of libcu++, the C++ Standard Library for your entire system,
// 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
// SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//

#ifndef _CUDA___MDSPAN_HOST_DEVICE_ACCESSOR
#define _CUDA___MDSPAN_HOST_DEVICE_ACCESSOR

#include <cuda/std/detail/__config>

#if defined(_CCCL_IMPLICIT_SYSTEM_HEADER_GCC)
# pragma GCC system_header
#elif defined(_CCCL_IMPLICIT_SYSTEM_HEADER_CLANG)
# pragma clang system_header
#elif defined(_CCCL_IMPLICIT_SYSTEM_HEADER_MSVC)
# pragma system_header
#endif // no system header

#include <cuda_runtime_api.h> // TODO: the code should compile even outside nvcc

#include <cuda/std/__type_traits/is_convertible.h>
#include <cuda/std/__type_traits/is_default_constructible.h>
#include <cuda/std/__type_traits/is_pointer.h>
#include <cuda/std/cassert>
#include <cuda/std/cstddef>
//
#include <cuda/std/__concepts/__concept_macros.h>

_LIBCUDACXX_BEGIN_NAMESPACE_CUDA

template <typename _Accessor>
struct __host_accessor;

template <typename _Accessor>
struct __device_accessor;

template <typename _Accessor>
struct __managed_accessor;

template <typename _Accessor>
using host_accessor = __host_accessor<_Accessor>;

template <typename _Accessor>
using device_accessor = __device_accessor<_Accessor>;

template <typename _Accessor>
using managed_accessor = __managed_accessor<_Accessor>;

/***********************************************************************************************************************
* Host/Device/Managed Accessor Traits
**********************************************************************************************************************/

template <typename>
inline constexpr bool is_host_accessor_v = false;

template <typename>
inline constexpr bool is_device_accessor_v = false;

template <typename>
inline constexpr bool is_managed_accessor_v = false;

template <typename _Accessor>
inline constexpr bool is_host_accessor_v<__host_accessor<_Accessor>> = true;

template <typename _Accessor>
inline constexpr bool is_device_accessor_v<__device_accessor<_Accessor>> = true;

template <typename _Accessor>
inline constexpr bool is_managed_accessor_v<__managed_accessor<_Accessor>> = true;

template <typename _Tp>
inline constexpr bool is_host_device_managed_accessor_v =
is_host_accessor_v<_Tp> || is_device_accessor_v<_Tp> || is_managed_accessor_v<_Tp>;

/***********************************************************************************************************************
* Host Accessor
**********************************************************************************************************************/

template <typename _Accessor>
struct __host_accessor : public _Accessor
{
static_assert(!is_host_device_managed_accessor_v<_Accessor>,
"cuda::__host_accessor/cuda::__device_accessor/cuda::__managed_accessor cannot be nested");

using offset_policy = __host_accessor<typename _Accessor::offset_policy>;
using data_handle_type = typename _Accessor::data_handle_type;
using reference = typename _Accessor::reference;
using element_type = typename _Accessor::element_type;

private:
using __self = __host_accessor<_Accessor>;

static constexpr bool __is_ctor_noexcept = noexcept(_Accessor{});
static constexpr bool __is_copy_ctor_noexcept = noexcept(_Accessor{_Accessor{}});
static constexpr bool __is_access_noexcept = noexcept(_Accessor{}.access(data_handle_type{}, 0));
static constexpr bool __is_offset_noexcept = noexcept(_Accessor{}.offset(data_handle_type{}, 0));

template <typename data_handle_type>
_LIBCUDACXX_HIDE_FROM_ABI static constexpr bool __is_host_accessible_pointer(data_handle_type __p) noexcept
{
if constexpr (_CUDA_VSTD::is_pointer_v<data_handle_type>)
{
cudaPointerAttributes __attrib;
_CCCL_VERIFY(::cudaPointerGetAttributes(&__attrib, __p) == ::cudaSuccess, "cudaPointerGetAttributes failed");
return __attrib.hostPointer != nullptr || __attrib.type == ::cudaMemoryTypeUnregistered;
}
else
{
return true; // cannot be verified
}
}

_LIBCUDACXX_HIDE_FROM_ABI static constexpr void __check_host_pointer(data_handle_type __p) noexcept
{
_CCCL_ASSERT(__self::__is_host_accessible_pointer(__p), "cuda::__host_accessor data handle is not a HOST pointer");
}

public:
_CCCL_TEMPLATE(typename _NotUsed = void)
_CCCL_REQUIRES(_CCCL_TRAIT(_CUDA_VSTD::is_default_constructible, _Accessor))
_LIBCUDACXX_HIDE_FROM_ABI __host_accessor() noexcept(__is_ctor_noexcept)
: _Accessor{}
{}

_CCCL_TEMPLATE(typename _OtherElementType)
_CCCL_REQUIRES(_CCCL_TRAIT(_CUDA_VSTD::is_convertible, _OtherElementType (*)[], element_type (*)[]))
_LIBCUDACXX_HIDE_FROM_ABI constexpr __host_accessor(__host_accessor<_OtherElementType>) noexcept(
__is_copy_ctor_noexcept)
{}

_CCCL_TEMPLATE(typename _OtherElementType)
_CCCL_REQUIRES(_CCCL_TRAIT(_CUDA_VSTD::is_convertible, _OtherElementType (*)[], element_type (*)[]))
_LIBCUDACXX_HIDE_FROM_ABI constexpr __host_accessor(__managed_accessor<_OtherElementType>) noexcept(
__is_copy_ctor_noexcept)
{}
Comment on lines +131 to +147
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In a previous review, I mentioned the issue that this omits all the conversions to and from _Accessor that the _Accessor class defines. I just want to mention it again so I don't forget : - ) . Conversions are an important part of an accessor's interface, because they help define conversions for mdspan itself. For example, without default_accessor's converting constructor, you can't assign mdspan-of-nonconst to mdspan-of-const. Section 5.8 of P2897 elaborates this explanation.

Should we consider instead a CRTP base class approach to __*_accessor? That would let developers of custom accessors opt into the run-time pointer checking functionality.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could be that you reviewed an older version? I previously added conversions from/to default_accessor for host_accessor and device_accessor.
Honestly, I'm was sure about managed_accessor because it looks unsafe...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we consider instead a CRTP base class approach to __*_accessor? That would let developers of custom accessors opt into the run-time pointer checking functionality.

the idea is nice. I'm a bit concerned about the implications for users. Could host_accessor be roughly defined in the following way?

class host_accessor : public __host_accessor<cuda::std::default_accessor> { .. };

Copy link
Contributor

@mhoemmen mhoemmen Feb 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fbusato wrote:

could be that you reviewed an older version? I previously added conversions from/to default_accessor for host_accessor and device_accessor.

The issue is with transitive conversions for a possibly generic _Accessor.

For example, suppose that I define two accessors A and B. A is weaker (more type-erased) than B. A defines an implicit conversion from B, and B defines an explicit conversion from A. (This follows the idiom that explicit conversions assert preconditions, while implicit conversions have no preconditions.) One example of this case is A = default_accessor<float>, and B = aligned_accessor<float, 16>.

struct A {
  // ... other accessor stuff ...
  A(B) {}
};

struct B {
  // ... other accessor stuff ...
  explicit B(A) {}
};

The problem is, these conversions don't carry over to {host,device,managed}_accessor. For example, host_accessor<A> does not have an implicit conversion from host_accessor<B>, and host_accessor<B> does not have an explicit conversion from host_accessor<A>.

Here's a draft for how to fix this. I would need to prototype this a bit to make sure it works.

template<class _Accessor>
class host_accessor {
private:
  _Accessor acc_;

  // ... details ...
public:
  // ... other constructors ...

  // This case covers host_accessor<default_accessor<OtherElementType>> as well.
  template<class InputAccessor>
  // If _Accessor is constructible from InputAccessor, then
  // host_accessor<_Accessor> is constructible from
  // host_accessor<InputAccessor>.
  requires(is_constructible<_Accessor, const InputAccessor&>)
  constexpr
  // Conversion is explicit if there is no implicit conversion
  // from InputAccessor to _Accessor.
  explicit(! is_convertible_v<const InputAccessor&, _Accessor>)
  host_accessor(const host_accessor<InputAccessor>& input_acc)
    : acc_(input_acc)
  {}
};

Copy link
Contributor

@mhoemmen mhoemmen Feb 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fbusato I just sent you an offline message with a link to an example of how to do this correctly. wrapper (see below) is like host_accessor or device_accessor -- an accessor that wraps another accessor. The link explains the design and includes tests and use examples.

template<class Accessor>
requires(not is_wrapper_v<Accessor>)
class wrapper : public Accessor {
public:
  using offset_policy = typename Accessor::offset_policy;
  using element_type = typename Accessor::element_type;
  using reference = typename Accessor::reference;
  using data_handle_type = typename Accessor::data_handle_type;

  constexpr wrapper() noexcept 
    requires(std::is_default_constructible_v<Accessor>)
  = default;

  // "Wrapping constructor" -- takes the inner accessor.
  constexpr wrapper(const Accessor& input_acc)
    : Accessor(input_acc)
  {}

  template<class OtherAccessor>
  requires(
    std::is_constructible_v<Accessor, OtherAccessor>
  )
  constexpr
  explicit(
    not std::is_convertible_v<OtherAccessor, Accessor>
  )
  wrapper(const wrapper<OtherAccessor>& input_acc)
    : Accessor(input_acc)
  {}

  constexpr reference
  access(data_handle_type p, size_t k) const {
    return Accessor::access(p, k);
  }

  constexpr typename offset_policy::data_handle_type
  offset(data_handle_type p, size_t k) const {
    return Accessor::offset(p, k);
  }
};


_LIBCUDACXX_HIDE_FROM_ABI constexpr reference access(data_handle_type __p, _CUDA_VSTD::size_t __i) const
noexcept(__is_access_noexcept)
{
NV_IF_ELSE_TARGET(NV_IS_HOST,
(__self::__check_host_pointer(__p);), //
(static_assert(false, "cuda::__host_accessor cannot be used in DEVICE code");))
return _Accessor::access(__p, __i);
}

_LIBCUDACXX_HIDE_FROM_ABI constexpr data_handle_type offset(data_handle_type __p, _CUDA_VSTD::size_t __i) const
noexcept(__is_offset_noexcept)
{
NV_IF_ELSE_TARGET(NV_IS_HOST,
(__self::__check_host_pointer(__p);), //
(static_assert(false, "cuda::__host_accessor cannot be used in DEVICE code");))
return _Accessor::offset(__p, __i);
}
};

/***********************************************************************************************************************
* Device Accessor
**********************************************************************************************************************/

template <typename _Accessor>
struct __device_accessor : public _Accessor
{
static_assert(!is_host_device_managed_accessor_v<_Accessor>,
"cuda::__host_accessor/cuda::__device_accessor/cuda::__managed_accessor cannot be nested");

using offset_policy = __device_accessor<typename _Accessor::offset_policy>;
using data_handle_type = typename _Accessor::data_handle_type;
using reference = typename _Accessor::reference;
using element_type = typename _Accessor::element_type;

private:
using __self = __device_accessor<_Accessor>;

static constexpr bool __is_ctor_noexcept = noexcept(_Accessor{});
static constexpr bool __is_copy_ctor_noexcept = noexcept(_Accessor{_Accessor{}});
static constexpr bool __is_access_noexcept = noexcept(_Accessor{}.access(data_handle_type{}, 0));
static constexpr bool __is_offset_noexcept = noexcept(_Accessor{}.offset(data_handle_type{}, 0));

template <typename _Sp = bool> // lazy evaluation
_LIBCUDACXX_HIDE_FROM_ABI static constexpr void __prevent_host_instantiation() noexcept
{
static_assert(sizeof(_Sp) != sizeof(_Sp), "cuda::__device_accessor cannot be used in HOST code");
}

public:
_CCCL_TEMPLATE(typename _NotUsed = void)
_CCCL_REQUIRES(_CCCL_TRAIT(_CUDA_VSTD::is_default_constructible, _Accessor))
_LIBCUDACXX_HIDE_FROM_ABI __device_accessor() noexcept(__is_ctor_noexcept)
: _Accessor{}
{}

_CCCL_TEMPLATE(typename _OtherElementType)
_CCCL_REQUIRES(_CCCL_TRAIT(_CUDA_VSTD::is_convertible, _OtherElementType (*)[], element_type (*)[]))
_LIBCUDACXX_HIDE_FROM_ABI constexpr __device_accessor(__device_accessor<_OtherElementType>) noexcept(
__is_copy_ctor_noexcept)
{}

_CCCL_TEMPLATE(typename _OtherElementType)
_CCCL_REQUIRES(_CCCL_TRAIT(_CUDA_VSTD::is_convertible, _OtherElementType (*)[], element_type (*)[]))
_LIBCUDACXX_HIDE_FROM_ABI constexpr __device_accessor(__managed_accessor<_OtherElementType>) noexcept(
__is_copy_ctor_noexcept)
{}

_LIBCUDACXX_HIDE_FROM_ABI constexpr reference access(data_handle_type __p, _CUDA_VSTD::size_t __i) const
noexcept(__is_access_noexcept)
{
NV_IF_TARGET(NV_IS_HOST, (__self::__prevent_host_instantiation();))
return _Accessor::access(__p, __i);
}

_LIBCUDACXX_HIDE_FROM_ABI constexpr data_handle_type offset(data_handle_type __p, _CUDA_VSTD::size_t __i) const
noexcept(__is_offset_noexcept)
{
NV_IF_TARGET(NV_IS_HOST, (__self::__prevent_host_instantiation();))
return _Accessor::offset(__p, __i);
}
};

/***********************************************************************************************************************
* Managed Accessor
**********************************************************************************************************************/

template <typename _Accessor>
struct __managed_accessor : public _Accessor
{
static_assert(!is_host_device_managed_accessor_v<_Accessor>,
"cuda::__host_accessor/cuda::__device_accessor/cuda::__managed_accessor cannot be nested");

using offset_policy = __managed_accessor<typename _Accessor::offset_policy>;
using data_handle_type = typename _Accessor::data_handle_type;
using reference = typename _Accessor::reference;
using element_type = typename _Accessor::element_type;

private:
using __self = __managed_accessor<_Accessor>;

static constexpr bool __is_ctor_noexcept = noexcept(_Accessor{});
static constexpr bool __is_copy_ctor_noexcept = noexcept(_Accessor{_Accessor{}});
static constexpr bool __is_access_noexcept = noexcept(_Accessor{}.access(data_handle_type{}, 0));
static constexpr bool __is_offset_noexcept = noexcept(_Accessor{}.offset(data_handle_type{}, 0));

static_assert(!is_host_device_managed_accessor_v<_Accessor>,
"cuda::__host_accessor/cuda::__device_accessor/cuda::__managed_accessor cannot be nested");

template <typename data_handle_type>
_LIBCUDACXX_HIDE_FROM_ABI static constexpr bool __is_managed_pointer(data_handle_type __p) noexcept
{
if constexpr (_CUDA_VSTD::is_pointer_v<data_handle_type>)
{
cudaPointerAttributes __attrib;
_CCCL_VERIFY(::cudaPointerGetAttributes(&__attrib, __p) == ::cudaSuccess, "cudaPointerGetAttributes failed");
return __attrib.devicePointer != nullptr && __attrib.hostPointer == __attrib.devicePointer;
}
else
{
return true; // cannot be verified
}
}

_LIBCUDACXX_HIDE_FROM_ABI static constexpr void __check_managed_pointer(data_handle_type __p) noexcept
{
_CCCL_ASSERT(__self::__is_managed_pointer(__p), "cuda::__managed_accessor data handle is not a MANAGED pointer");
}

public:
_CCCL_TEMPLATE(typename _NotUsed = void)
_CCCL_REQUIRES(_CCCL_TRAIT(_CUDA_VSTD::is_default_constructible, _Accessor))
_LIBCUDACXX_HIDE_FROM_ABI __managed_accessor() noexcept(__is_ctor_noexcept)
: _Accessor{}
{}

_CCCL_TEMPLATE(typename _OtherElementType)
_CCCL_REQUIRES(_CCCL_TRAIT(_CUDA_VSTD::is_convertible, _OtherElementType (*)[], element_type (*)[]))
_LIBCUDACXX_HIDE_FROM_ABI constexpr __managed_accessor(__managed_accessor<_OtherElementType>) noexcept(
__is_copy_ctor_noexcept)
{}

_LIBCUDACXX_HIDE_FROM_ABI constexpr reference access(data_handle_type __p, _CUDA_VSTD::size_t __i) const
noexcept(__is_access_noexcept)
{
NV_IF_TARGET(NV_IS_HOST, (__self::__check_managed_pointer(__p);))
return _Accessor::access(__p, __i);
}

_LIBCUDACXX_HIDE_FROM_ABI constexpr data_handle_type offset(data_handle_type __p, _CUDA_VSTD::size_t __i) const
noexcept(__is_offset_noexcept)
{
NV_IF_TARGET(NV_IS_HOST, (__self::__check_managed_pointer(__p);))
return _Accessor::offset(__p, __i);
}
};

/***********************************************************************************************************************
* Accessibility Traits
**********************************************************************************************************************/

template <typename>
inline constexpr bool is_host_accessible_v = false;

template <typename>
inline constexpr bool is_device_accessible_v = false;

template <typename _Accessor>
inline constexpr bool is_host_accessible_v<__host_accessor<_Accessor>> = true;

template <typename _Accessor>
inline constexpr bool is_host_accessible_v<__managed_accessor<_Accessor>> = true;

template <template <typename> class _TClass, typename _Accessor>
inline constexpr bool is_host_accessible_v<_TClass<_Accessor>> = is_host_accessible_v<_Accessor>;

template <typename _Accessor>
inline constexpr bool is_device_accessible_v<__device_accessor<_Accessor>> = true;

template <typename _Accessor>
inline constexpr bool is_device_accessible_v<__managed_accessor<_Accessor>> = true;

template <template <typename> class _TClass, typename _Accessor>
inline constexpr bool is_device_accessible_v<_TClass<_Accessor>> = is_device_accessible_v<_Accessor>;

_LIBCUDACXX_END_NAMESPACE_CUDA

#endif // _CUDA___MDSPAN_HOST_DEVICE_ACCESSOR
59 changes: 59 additions & 0 deletions libcudacxx/include/cuda/__mdspan/host_device_mdspan.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//===----------------------------------------------------------------------===//
//
// Part of libcu++, the C++ Standard Library for your entire system,
// 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
// SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//

#ifndef _CUDA___MDSPAN_HOST_DEVICE_MDSPAN
#define _CUDA___MDSPAN_HOST_DEVICE_MDSPAN

#include <cuda/std/detail/__config>

#if defined(_CCCL_IMPLICIT_SYSTEM_HEADER_GCC)
# pragma GCC system_header
#elif defined(_CCCL_IMPLICIT_SYSTEM_HEADER_CLANG)
# pragma clang system_header
#elif defined(_CCCL_IMPLICIT_SYSTEM_HEADER_MSVC)
# pragma system_header
#endif // no system header

#include <cuda/__mdspan/host_device_accessor.h>
#include <cuda/std/mdspan>

_LIBCUDACXX_BEGIN_NAMESPACE_CUDA

template <typename _ElementType,
typename _Extents,
typename _LayoutPolicy = _CUDA_VSTD::layout_right,
typename _AccessorPolicy = _CUDA_VSTD::default_accessor<_ElementType>>
using host_mdspan = _CUDA_VSTD::mdspan<_ElementType, _Extents, _LayoutPolicy, host_accessor<_AccessorPolicy>>;

template <typename _ElementType,
typename _Extents,
typename _LayoutPolicy = _CUDA_VSTD::layout_right,
typename _AccessorPolicy = _CUDA_VSTD::default_accessor<_ElementType>>
using device_mdspan = _CUDA_VSTD::mdspan<_ElementType, _Extents, _LayoutPolicy, device_accessor<_AccessorPolicy>>;

template <typename _ElementType,
typename _Extents,
typename _LayoutPolicy = _CUDA_VSTD::layout_right,
typename _AccessorPolicy = _CUDA_VSTD::default_accessor<_ElementType>>
using managed_mdspan = _CUDA_VSTD::mdspan<_ElementType, _Extents, _LayoutPolicy, managed_accessor<_AccessorPolicy>>;

/***********************************************************************************************************************
* Accessibility Traits
**********************************************************************************************************************/

template <typename _Tp, typename _Ep, typename _Lp, typename _Ap>
inline constexpr bool is_host_accessible_v<_CUDA_VSTD::mdspan<_Tp, _Ep, _Lp, _Ap>> = is_host_accessible_v<_Ap>;

template <typename _Tp, typename _Ep, typename _Lp, typename _Ap>
inline constexpr bool is_device_accessible_v<_CUDA_VSTD::mdspan<_Tp, _Ep, _Lp, _Ap>> = is_device_accessible_v<_Ap>;

_LIBCUDACXX_END_NAMESPACE_CUDA

#endif // _CUDA___MDSPAN_HOST_DEVICE_MDSPAN
Loading