//===----------------------------------------------------------------------===// // // Part of the libcu++ 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 // SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. // //===----------------------------------------------------------------------===// #include #include #include "test_macros.h" //---------------------------------------------------------------------------------------------------------------------- // USER 1 CODE namespace user1 { template class AccessorB; // AccessorA is more type-erased than AccessorB. template class AccessorA { public: using offset_policy = AccessorA; using element_type = ElementType; using reference = ElementType&; using data_handle_type = ElementType*; constexpr AccessorA() noexcept = default; template , int> = 0> __host__ __device__ constexpr AccessorA(const AccessorA&) noexcept {} // Conversion from AccessorB to AccessorA type-erases; it has no preconditions, and can therefore be safely implicit. template , int> = 0> __host__ __device__ constexpr AccessorA(const AccessorB&) noexcept {} __host__ __device__ constexpr reference access(data_handle_type p, size_t i) const noexcept { return p[i]; } __host__ __device__ constexpr typename offset_policy::data_handle_type offset(data_handle_type p, size_t i) const noexcept { return p + i; } }; template class AccessorB { public: using offset_policy = AccessorA; using element_type = ElementType; using reference = ElementType&; using data_handle_type = ElementType*; constexpr AccessorB() noexcept = default; template , int> = 0> __host__ __device__ constexpr AccessorB(const AccessorB&) noexcept {} // Conversion from AccessorA to AccessorB asserts a precondition; // it un-type-erases from less specific AccessorA to more specific AccessorB. Thus, it is explicit. template , int> = 0> __host__ __device__ constexpr explicit AccessorB(const AccessorA&) noexcept {} __host__ __device__ constexpr reference access(data_handle_type p, size_t i) const noexcept { return p[i]; } __host__ __device__ constexpr typename offset_policy::data_handle_type offset(data_handle_type p, size_t i) const noexcept { return p + i; } }; } // namespace user1 //---------------------------------------------------------------------------------------------------------------------- // USER 2 CODE namespace user2 { // Imagine that a different user1 writes AccessorC. // They can't change the public interface of AccessorA, but they want to permit a conversion from AccessorC to // AccessorA. AccessorA happens to be more type-erased than AccessorC, so this is a conversion without preconditions; // therefore, it's not explicit. template class AccessorC { public: using offset_policy = AccessorC; using element_type = ElementType; using reference = ElementType&; using data_handle_type = ElementType*; constexpr AccessorC() noexcept = default; template , int> = 0> __host__ __device__ constexpr AccessorC(const AccessorC&) noexcept {} __host__ __device__ constexpr operator user1::AccessorA() const noexcept { return {}; } __host__ __device__ constexpr reference access(data_handle_type p, size_t i) const noexcept { return p[i]; } __host__ __device__ constexpr typename offset_policy::data_handle_type offset(data_handle_type p, size_t i) const noexcept { return p + i; } }; // Imagine that a different user1 writes AccessorD. // They can't change the public interface of AccessorB, but they want to permit a conversion from AccessorD to // AccessorB. This is a conversion with preconditions, so it's explicit. template class AccessorD { public: using offset_policy = AccessorD; using element_type = ElementType; using reference = ElementType&; using data_handle_type = ElementType*; constexpr AccessorD() noexcept = default; template , int> = 0> __host__ __device__ constexpr AccessorD(const AccessorD&) noexcept {} __host__ __device__ constexpr explicit operator user1::AccessorB() const noexcept { return {}; } __host__ __device__ constexpr reference access(data_handle_type p, size_t i) const noexcept { return p[i]; } __host__ __device__ constexpr typename offset_policy::data_handle_type offset(data_handle_type p, size_t i) const noexcept { return p + i; } }; } // namespace user2 //---------------------------------------------------------------------------------------------------------------------- // TEST CODE __host__ __device__ void test_host_device_accessor_conversions() { using user1::AccessorA; using user1::AccessorB; using WrapperA = cuda::__restrict_accessor>; using WrapperAconst = cuda::__restrict_accessor>; using WrapperB = cuda::__restrict_accessor>; { // Test CTAD with wrapping constructor WrapperA wrapper_acc1(AccessorA{}); static_assert(cuda::std::is_same_v); WrapperAconst wrapper_acc_const1(AccessorA{}); static_assert(cuda::std::is_same_v); // Test CTAD with copy constructor WrapperA wrapper_acc2{wrapper_acc1}; static_assert(cuda::std::is_same_v); WrapperAconst wrapper_acc_const2{wrapper_acc_const1}; static_assert(cuda::std::is_same_v); unused(wrapper_acc2); unused(wrapper_acc_const2); // Test converting constructor: cuda::__restrict_accessor>(AccessorA) WrapperAconst wrapper_acc_const3{wrapper_acc1}; unused(wrapper_acc_const3); // Test implicit conversion: cuda::__restrict_accessor> -> cuda::__restrict_accessor> auto f = [](const WrapperA& wrapper_acc1) -> WrapperAconst { return wrapper_acc1; }; unused(f(WrapperA{})); } { // Test (explicit) converting constructor: cuda::__restrict_accessor>(AccessorA) WrapperB wrapper_acc3{WrapperA{}}; // Test implicit conversion from AccessorB to AccessorA (type erasure) auto f1 = [](const WrapperB& wrapper_acc1) -> WrapperA { return wrapper_acc1; }; unused(f1(wrapper_acc3)); // Test implicit conversion from AccessorB to AccessorA (type erasure) auto f2 = [](const WrapperB& wrapper_acc1) -> WrapperAconst { return wrapper_acc1; }; unused(f2(wrapper_acc3)); // Test that implicit conversion from AccessorA to AccessorB is forbidden auto f3 = [](const WrapperA& wrapper_acc1) -> WrapperB { return WrapperB{wrapper_acc1}; // return wrapper_acc1; // rightfully does not compile }; unused(f3(WrapperA{})); } } __host__ __device__ void test_conversion() { using user1::AccessorA; using user1::AccessorB; using user2::AccessorC; using user2::AccessorD; using WrapperA = cuda::__restrict_accessor>; using WrapperB = cuda::__restrict_accessor>; using WrapperC = cuda::__restrict_accessor>; using WrapperD = cuda::__restrict_accessor>; { // Test explicit and implicit conversion from cuda::__restrict_accessor> to // cuda::__restrict_accessor>. This works because cuda::__restrict_accessor> publicly // inherits from AccessorC, so it inherits AccessorC's conversion operators. WrapperA wrapper_acc1{WrapperC{}}; auto f1 = [](const WrapperA& wrapper_acc1) -> WrapperA { return wrapper_acc1; }; unused(f1(WrapperC{})); } { // Test explicit conversion from cuda::__restrict_accessor> to cuda::__restrict_accessor>. // This works because cuda::__restrict_accessor> publicly inherits from AccessorD, // so it inherits AccessorD's conversion operators. WrapperB wrapper_acc1{WrapperD{}}; auto f1 = [](const WrapperD& w) -> WrapperB { return WrapperB{}; // return w; // rightfully does not compile }; unused(f1(WrapperD{})); } } // Application: conversion of cuda::__restrict_accessor> // to cuda::__restrict_accessor>. __host__ __device__ void test_aligned_to_default() { using WrapperDefault = cuda::__restrict_accessor>; using WrapperAligned = cuda::__restrict_accessor>; WrapperAligned wrapper_align_acc{cuda::std::aligned_accessor{}}; auto f = [](const WrapperAligned& w) -> WrapperDefault { return w; }; unused(f(wrapper_align_acc)); } // Application: Explicit conversion of cuda::__restrict_accessor> // to cuda::__restrict_accessor>. __host__ __device__ void test_default_to_aligned() { using WrapperDefault = cuda::__restrict_accessor>; using WrapperAligned = cuda::__restrict_accessor>; WrapperDefault wrapper_default_acc{cuda::std::default_accessor{}}; WrapperAligned wrapper_aligned_acc{wrapper_default_acc}; auto f = [](const WrapperDefault& w) -> WrapperAligned { return WrapperAligned{w}; // return w; // rightfully does not compile }; unused(wrapper_aligned_acc); unused(f(wrapper_default_acc)); } int main(int, char**) { test_host_device_accessor_conversions(); test_conversion(); test_aligned_to_default(); test_default_to_aligned(); return 0; }