diff --git a/libcudacxx/include/cuda/__memory_resource/any_resource.h b/libcudacxx/include/cuda/__memory_resource/any_resource.h index 04d50af6c3b..f8410abdb13 100644 --- a/libcudacxx/include/cuda/__memory_resource/any_resource.h +++ b/libcudacxx/include/cuda/__memory_resource/any_resource.h @@ -4,7 +4,7 @@ // 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. +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. // //===----------------------------------------------------------------------===// @@ -225,6 +225,10 @@ struct __with_try_get_property } }; +// Tag type for constructing type-erased resource wrappers from their base __basic_any +struct __from_base_tag +{}; + _CCCL_BEGIN_NAMESPACE_ABI_VER4_BUMP template @@ -255,6 +259,12 @@ struct _CCCL_DECLSPEC_EMPTY_BASES any_synchronous_resource using default_queries = ::cuda::mr::properties_list<_Properties...>; + //! @cond + explicit any_synchronous_resource(__from_base_tag, __base&& __b) noexcept + : __base(::cuda::std::move(__b)) + {} + //! @endcond + private: using __base::interface; }; @@ -274,6 +284,12 @@ struct _CCCL_DECLSPEC_EMPTY_BASES any_resource using default_queries = ::cuda::mr::properties_list<_Properties...>; + //! @cond + explicit any_resource(__from_base_tag, __base&& __b) noexcept + : __base(::cuda::std::move(__b)) + {} + //! @endcond + private: template friend struct any_synchronous_resource; @@ -330,6 +346,12 @@ struct _CCCL_DECLSPEC_EMPTY_BASES synchronous_resource_ref using default_queries = ::cuda::mr::properties_list<_Properties...>; + //! @cond + explicit synchronous_resource_ref(__from_base_tag, __base&& __b) noexcept + : __base(::cuda::std::move(__b)) + {} + //! @endcond + private: template friend struct synchronous_resource_ref; @@ -378,6 +400,12 @@ struct _CCCL_DECLSPEC_EMPTY_BASES resource_ref using default_queries = ::cuda::mr::properties_list<_Properties...>; + //! @cond + explicit resource_ref(__from_base_tag, __base&& __b) noexcept + : __base(::cuda::std::move(__b)) + {} + //! @endcond + private: template friend struct synchronous_resource_ref; @@ -953,6 +981,140 @@ auto make_any_resource(_Args&&... __args) -> any_resource<_Properties...> return any_resource<_Properties...>{::cuda::std::in_place_type<_Resource>, ::cuda::std::forward<_Args>(__args)...}; } +// ── resource_cast ─────────────────────────────────────────────────────────── +// +// Extracts a pointer to the concrete resource type stored inside a type-erased +// resource wrapper (any_resource, any_synchronous_resource, resource_ref, +// synchronous_resource_ref). Returns nullptr if the stored type does not +// match _Tp. + +//! @brief Extracts a pointer to the concrete resource type \c _Tp from a +//! type-erased resource wrapper. +//! @tparam _Tp The concrete resource type to extract. +//! @param __res Pointer to the type-erased resource wrapper. +//! @return A pointer to the stored object of type \c _Tp, or \c nullptr if +//! the stored type does not match \c _Tp. +template +[[nodiscard]] _CCCL_HOST_API auto resource_cast(any_resource<_Properties...>* __res) noexcept -> _Tp* +{ + static_assert(::cuda::std::is_void_v<_Tp> || ::cuda::mr::resource_with<_Tp, _Properties...>, + "_Tp must be void or satisfy resource_with<_Tp, _Properties...>"); + // Use static_cast to the __basic_any base to work around GCC < 11 template argument deduction issues. + return ::cuda::__any_cast<_Tp>(static_cast<::cuda::__basic_any<__iasync_resource<_Properties...>>*>(__res)); +} + +//! @overload +template +[[nodiscard]] _CCCL_HOST_API auto resource_cast(const any_resource<_Properties...>* __res) noexcept -> const _Tp* +{ + static_assert(::cuda::std::is_void_v<_Tp> || ::cuda::mr::resource_with<_Tp, _Properties...>, + "_Tp must be void or satisfy resource_with<_Tp, _Properties...>"); + return ::cuda::__any_cast<_Tp>(static_cast>*>(__res)); +} + +//! @overload +template +[[nodiscard]] _CCCL_HOST_API auto resource_cast(any_synchronous_resource<_Properties...>* __res) noexcept -> _Tp* +{ + static_assert(::cuda::std::is_void_v<_Tp> || ::cuda::mr::synchronous_resource_with<_Tp, _Properties...>, + "_Tp must be void or satisfy synchronous_resource_with<_Tp, _Properties...>"); + return ::cuda::__any_cast<_Tp>(static_cast<::cuda::__basic_any<__iresource<_Properties...>>*>(__res)); +} + +//! @overload +template +[[nodiscard]] _CCCL_HOST_API auto resource_cast(const any_synchronous_resource<_Properties...>* __res) noexcept + -> const _Tp* +{ + static_assert(::cuda::std::is_void_v<_Tp> || ::cuda::mr::synchronous_resource_with<_Tp, _Properties...>, + "_Tp must be void or satisfy synchronous_resource_with<_Tp, _Properties...>"); + return ::cuda::__any_cast<_Tp>(static_cast>*>(__res)); +} + +//! @overload +template +[[nodiscard]] _CCCL_HOST_API auto resource_cast(resource_ref<_Properties...>* __res) noexcept -> _Tp* +{ + static_assert(::cuda::std::is_void_v<_Tp> || ::cuda::mr::resource_with<_Tp, _Properties...>, + "_Tp must be void or satisfy resource_with<_Tp, _Properties...>"); + return ::cuda::__any_cast<_Tp>(static_cast<::cuda::__basic_any<__iasync_resource<_Properties...>&>*>(__res)); +} + +//! @overload +template +[[nodiscard]] _CCCL_HOST_API auto resource_cast(const resource_ref<_Properties...>* __res) noexcept -> const _Tp* +{ + static_assert(::cuda::std::is_void_v<_Tp> || ::cuda::mr::resource_with<_Tp, _Properties...>, + "_Tp must be void or satisfy resource_with<_Tp, _Properties...>"); + return ::cuda::__any_cast<_Tp>(static_cast&>*>(__res)); +} + +//! @overload +template +[[nodiscard]] _CCCL_HOST_API auto resource_cast(synchronous_resource_ref<_Properties...>* __res) noexcept -> _Tp* +{ + static_assert(::cuda::std::is_void_v<_Tp> || ::cuda::mr::synchronous_resource_with<_Tp, _Properties...>, + "_Tp must be void or satisfy synchronous_resource_with<_Tp, _Properties...>"); + return ::cuda::__any_cast<_Tp>(static_cast<::cuda::__basic_any<__iresource<_Properties...>&>*>(__res)); +} + +//! @overload +template +[[nodiscard]] _CCCL_HOST_API auto resource_cast(const synchronous_resource_ref<_Properties...>* __res) noexcept + -> const _Tp* +{ + static_assert(::cuda::std::is_void_v<_Tp> || ::cuda::mr::synchronous_resource_with<_Tp, _Properties...>, + "_Tp must be void or satisfy synchronous_resource_with<_Tp, _Properties...>"); + return ::cuda::__any_cast<_Tp>(static_cast&>*>(__res)); +} + +// ── dynamic_resource_cast ─────────────────────────────────────────────────── +// +// Dynamically casts between type-erased resource wrappers that have different +// property sets, using runtime information to validate the conversion. + +//! @brief Dynamically casts a type-erased resource to a different property set. +//! @tparam _DstProperties The destination property types (deduced from the +//! destination resource type template argument). +//! @param __src The source resource to cast from. +//! @return A new type-erased resource wrapper with the destination properties. +//! @throws __bad_any_cast if the runtime type does not support the destination +//! interface. +template +[[nodiscard]] _CCCL_HOST_API auto dynamic_resource_cast(any_resource<_SrcProperties...>&& __src) + -> any_resource<_DstProperties...> +{ + return any_resource<_DstProperties...>{ + __from_base_tag{}, ::cuda::__dynamic_any_cast<__iasync_resource<_DstProperties...>>(::cuda::std::move(__src))}; +} + +//! @overload +template +[[nodiscard]] _CCCL_HOST_API auto dynamic_resource_cast(any_synchronous_resource<_SrcProperties...>&& __src) + -> any_synchronous_resource<_DstProperties...> +{ + return any_synchronous_resource<_DstProperties...>{ + __from_base_tag{}, ::cuda::__dynamic_any_cast<__iresource<_DstProperties...>>(::cuda::std::move(__src))}; +} + +//! @overload +template +[[nodiscard]] _CCCL_HOST_API auto dynamic_resource_cast(resource_ref<_SrcProperties...>* __src) + -> resource_ref<_DstProperties...> +{ + return resource_ref<_DstProperties...>{ + __from_base_tag{}, ::cuda::__dynamic_any_cast<__iasync_resource<_DstProperties...>&>(*__src)}; +} + +//! @overload +template +[[nodiscard]] _CCCL_HOST_API auto dynamic_resource_cast(synchronous_resource_ref<_SrcProperties...>* __src) + -> synchronous_resource_ref<_DstProperties...> +{ + return synchronous_resource_ref<_DstProperties...>{ + __from_base_tag{}, ::cuda::__dynamic_any_cast<__iresource<_DstProperties...>&>(*__src)}; +} + _CCCL_END_NAMESPACE_CUDA_MR # include diff --git a/libcudacxx/include/cuda/__utility/__basic_any/rtti.h b/libcudacxx/include/cuda/__utility/__basic_any/rtti.h index 5a83d49735c..3298265fca4 100644 --- a/libcudacxx/include/cuda/__utility/__basic_any/rtti.h +++ b/libcudacxx/include/cuda/__utility/__basic_any/rtti.h @@ -4,7 +4,7 @@ // 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. +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. // //===----------------------------------------------------------------------===// @@ -143,9 +143,13 @@ struct __rtti : __rtti_base [[nodiscard]] _CCCL_API auto __query_interface(__iset_<_Interfaces...>) const noexcept -> __vptr_for<__iset_<_Interfaces...>> { - // TODO: find a way to check at runtime that the requested __iset_ is a subset - // of the interfaces in the vtable. - return static_cast<__vptr_for<__iset_<_Interfaces...>>>(this); + // Verify that every sub-interface in the requested set exists in the vtable. + // TODO: Can it be done in a more efficient way? + if ((__query_interface(_Interfaces{}) && ...)) + { + return static_cast<__vptr_for<__iset_<_Interfaces...>>>(this); + } + return nullptr; } // Sequentially search the base_vptr_map for the requested interface by diff --git a/libcudacxx/test/libcudacxx/cuda/memory_resource/any_resource/resource_cast.cu b/libcudacxx/test/libcudacxx/cuda/memory_resource/any_resource/resource_cast.cu new file mode 100644 index 00000000000..21e8486e870 --- /dev/null +++ b/libcudacxx/test/libcudacxx/cuda/memory_resource/any_resource/resource_cast.cu @@ -0,0 +1,301 @@ +//===----------------------------------------------------------------------===// +// +// Part of CUDA Experimental in CUDA C++ Core Libraries, +// 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) 2026 NVIDIA CORPORATION & AFFILIATES. +// +//===----------------------------------------------------------------------===// + +#include + +#include + +#include "../test_resource.cuh" // IWYU pragma: keep + +#ifndef __CUDA_ARCH__ + +TEST_CASE("resource_cast from any_resource", "[container][resource]") +{ + test_fixture_ fixture; + big_resource stored{42, &fixture}; + cuda::mr::any_resource mr{stored}; + + SECTION("matching type returns non-null") + { + auto* ptr = cuda::mr::resource_cast(&mr); + REQUIRE(ptr != nullptr); + CHECK(ptr->data == 42); + } + + SECTION("mismatched type returns null") + { + auto* ptr = cuda::mr::resource_cast(&mr); + CHECK(ptr == nullptr); + } + + SECTION("const overload") + { + const auto& cmr = mr; + auto* ptr = cuda::mr::resource_cast(&cmr); + REQUIRE(ptr != nullptr); + CHECK(ptr->data == 42); + } +} + +TEST_CASE("resource_cast from any_synchronous_resource", "[container][resource]") +{ + test_fixture_ fixture; + cuda::mr::any_synchronous_resource mr{big_resource{7, &fixture}}; + + auto* ptr = cuda::mr::resource_cast(&mr); + REQUIRE(ptr != nullptr); + CHECK(ptr->data == 7); + + CHECK(cuda::mr::resource_cast(&mr) == nullptr); +} + +TEST_CASE("resource_cast from resource_ref", "[container][resource]") +{ + test_fixture_ fixture; + big_resource stored{99, &fixture}; + cuda::mr::resource_ref ref{stored}; + + auto* ptr = cuda::mr::resource_cast(&ref); + REQUIRE(ptr != nullptr); + CHECK(ptr->data == 99); + CHECK(ptr == &stored); + + CHECK(cuda::mr::resource_cast(&ref) == nullptr); +} + +TEST_CASE("resource_cast from synchronous_resource_ref", "[container][resource]") +{ + test_fixture_ fixture; + big_resource stored{55, &fixture}; + cuda::mr::synchronous_resource_ref ref{stored}; + + auto* ptr = cuda::mr::resource_cast(&ref); + REQUIRE(ptr != nullptr); + CHECK(ptr->data == 55); + + CHECK(cuda::mr::resource_cast(&ref) == nullptr); +} + +TEST_CASE("resource_cast with void extracts raw pointer", "[container][resource]") +{ + test_fixture_ fixture; + cuda::mr::any_resource mr{big_resource{1, &fixture}}; + + auto* vptr = cuda::mr::resource_cast(&mr); + CHECK(vptr != nullptr); + + const auto& cmr = mr; + const auto* cvptr = cuda::mr::resource_cast(&cmr); + CHECK(cvptr != nullptr); + CHECK(cvptr == vptr); +} + +TEST_CASE("resource_cast on empty any_resource returns null", "[container][resource]") +{ + cuda::mr::any_resource mr{}; + CHECK(cuda::mr::resource_cast(&mr) == nullptr); +} + +TEST_CASE("resource_cast on nullptr input returns null", "[container][resource]") +{ + cuda::mr::any_resource* ptr = nullptr; + CHECK(cuda::mr::resource_cast(ptr) == nullptr); + + const cuda::mr::any_resource* cptr = nullptr; + CHECK(cuda::mr::resource_cast(cptr) == nullptr); +} + +TEST_CASE("resource_cast on moved-from any_resource returns null", "[container][resource]") +{ + test_fixture_ fixture; + cuda::mr::any_resource src{big_resource{42, &fixture}}; + auto dst = ::cuda::std::move(src); + + // moved-from source should be empty + CHECK(cuda::mr::resource_cast(&src) == nullptr); + + // destination should have the value + auto* ptr = cuda::mr::resource_cast(&dst); + REQUIRE(ptr != nullptr); + CHECK(ptr->data == 42); +} + +// A derived resource type for testing that resource_cast uses exact type matching +struct derived_resource : big_resource +{ + using big_resource::big_resource; + using default_queries = cuda::mr::properties_list<>; +}; + +TEST_CASE("resource_cast uses exact type matching, not derived-to-base", "[container][resource]") +{ + test_fixture_ fixture; + derived_resource stored{99, &fixture}; + cuda::mr::any_resource mr{stored}; + + // Exact type match succeeds + auto* exact = cuda::mr::resource_cast(&mr); + REQUIRE(exact != nullptr); + CHECK(exact->data == 99); + + // Base type does NOT match — resource_cast uses exact type identity + auto* base = cuda::mr::resource_cast(&mr); + CHECK(base == nullptr); +} + +// ── dynamic_resource_cast ─────────────────────────────────────────────────── + +TEST_CASE("dynamic_resource_cast any_resource narrowing", "[container][resource]") +{ + test_fixture_ fixture; + big_resource stored{42, &fixture}; + + SECTION("narrow to one property") + { + cuda::mr::any_resource src{stored}; + auto dst = cuda::mr::dynamic_resource_cast(::cuda::std::move(src)); + CHECK(dst.has_value()); + auto* ptr = cuda::mr::resource_cast(&dst); + REQUIRE(ptr != nullptr); + CHECK(ptr->data == 42); + } + + SECTION("narrow to the other property") + { + cuda::mr::any_resource src{stored}; + auto dst = cuda::mr::dynamic_resource_cast(::cuda::std::move(src)); + CHECK(dst.has_value()); + auto* ptr = cuda::mr::resource_cast(&dst); + REQUIRE(ptr != nullptr); + CHECK(ptr->data == 42); + } + +# if _CCCL_HAS_EXCEPTIONS() + SECTION("cast to unsupported property throws") + { + cuda::mr::any_resource src{stored}; + CHECK_THROWS_AS(cuda::mr::dynamic_resource_cast(::cuda::std::move(src)), + cuda::__bad_any_cast); + } + + SECTION("cast to undeclared property throws") + { + // extra_property is supported by big_resource but not in the wrapper's interface + cuda::mr::any_resource src{stored}; + CHECK_THROWS_AS(cuda::mr::dynamic_resource_cast(::cuda::std::move(src)), cuda::__bad_any_cast); + } +# endif // _CCCL_HAS_EXCEPTIONS() +} + +TEST_CASE("dynamic_resource_cast any_resource narrow-then-widen round-trip", "[container][resource]") +{ + test_fixture_ fixture; + big_resource stored{42, &fixture}; + + // Start wide, narrow, then widen back — vtable retains the original entries + cuda::mr::any_resource wide{stored}; + auto narrow = cuda::mr::dynamic_resource_cast(::cuda::std::move(wide)); + CHECK(narrow.has_value()); + auto restored = cuda::mr::dynamic_resource_cast(::cuda::std::move(narrow)); + CHECK(restored.has_value()); + CHECK(try_get_property(restored, extra_property{}) == true); + auto* ptr = cuda::mr::resource_cast(&restored); + REQUIRE(ptr != nullptr); + CHECK(ptr->data == 42); +} + +TEST_CASE("dynamic_resource_cast any_synchronous_resource", "[container][resource]") +{ + test_fixture_ fixture; + big_resource stored{7, &fixture}; + + SECTION("valid narrowing cast") + { + cuda::mr::any_synchronous_resource src{stored}; + auto dst = cuda::mr::dynamic_resource_cast(::cuda::std::move(src)); + CHECK(dst.has_value()); + auto* ptr = cuda::mr::resource_cast(&dst); + REQUIRE(ptr != nullptr); + CHECK(ptr->data == 7); + } + +# if _CCCL_HAS_EXCEPTIONS() + SECTION("invalid cast throws") + { + cuda::mr::any_synchronous_resource src{stored}; + CHECK_THROWS_AS(cuda::mr::dynamic_resource_cast(::cuda::std::move(src)), + cuda::__bad_any_cast); + } +# endif // _CCCL_HAS_EXCEPTIONS() +} + +TEST_CASE("dynamic_resource_cast resource_ref", "[container][resource]") +{ + test_fixture_ fixture; + big_resource stored{99, &fixture}; + + SECTION("valid narrowing cast") + { + cuda::mr::resource_ref ref{stored}; + auto dst = cuda::mr::dynamic_resource_cast(&ref); + auto* ptr = cuda::mr::resource_cast(&dst); + REQUIRE(ptr != nullptr); + CHECK(ptr->data == 99); + } + +# if _CCCL_HAS_EXCEPTIONS() + SECTION("invalid cast throws") + { + cuda::mr::resource_ref ref{stored}; + CHECK_THROWS_AS(cuda::mr::dynamic_resource_cast(&ref), cuda::__bad_any_cast); + } +# endif // _CCCL_HAS_EXCEPTIONS() +} + +TEST_CASE("dynamic_resource_cast synchronous_resource_ref", "[container][resource]") +{ + test_fixture_ fixture; + big_resource stored{55, &fixture}; + + SECTION("valid narrowing cast") + { + cuda::mr::synchronous_resource_ref ref{stored}; + auto dst = cuda::mr::dynamic_resource_cast(&ref); + auto* ptr = cuda::mr::resource_cast(&dst); + REQUIRE(ptr != nullptr); + CHECK(ptr->data == 55); + } + +# if _CCCL_HAS_EXCEPTIONS() + SECTION("invalid cast throws") + { + cuda::mr::synchronous_resource_ref ref{stored}; + CHECK_THROWS_AS(cuda::mr::dynamic_resource_cast(&ref), cuda::__bad_any_cast); + } +# endif // _CCCL_HAS_EXCEPTIONS() +} + +// ── void property vtable entry verification ───────────────────────────────── + +TEST_CASE("try_get_property for void properties respects concrete type", "[container][resource]") +{ + // big_resource provides host_accessible and extra_property, but NOT device_accessible + test_fixture_ fixture; + big_resource stored{1, &fixture}; + cuda::mr::any_resource mr{stored}; + + // Provided by big_resource — should be true + CHECK(try_get_property(mr, cuda::mr::host_accessible{}) == true); + + // NOT provided by big_resource — should be false + CHECK(try_get_property(mr, cuda::mr::device_accessible{}) == false); +} + +#endif // __CUDA_ARCH__ diff --git a/libcudacxx/test/libcudacxx/cuda/utility/basic_any.pass.cpp b/libcudacxx/test/libcudacxx/cuda/utility/basic_any.pass.cpp index aff9f664958..764c881236a 100644 --- a/libcudacxx/test/libcudacxx/cuda/utility/basic_any.pass.cpp +++ b/libcudacxx/test/libcudacxx/cuda/utility/basic_any.pass.cpp @@ -3,7 +3,7 @@ // 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. +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. // //===----------------------------------------------------------------------===// @@ -615,8 +615,120 @@ _CCCL_HOST_DEVICE void test_basic_any() test.test_basic_any_test_for_emplacing_immovable_object(); } +// ── __iset_ cross-cast validation ─────────────────────────────────────────── +// +// Tests that __dynamic_any_cast between __iset_ types correctly validates +// that every sub-interface in the destination set exists in the source's vtable. + +template +struct iprop_a : cuda::__basic_interface +{ + _CCCL_HOST_DEVICE int get_a() + { + return cuda::__virtcall<&iprop_a::get_a>(this); + } + + template + using overrides = cuda::__overrides_for; +}; + +template +struct iprop_b : cuda::__basic_interface +{ + _CCCL_HOST_DEVICE int get_b() + { + return cuda::__virtcall<&iprop_b::get_b>(this); + } + + template + using overrides = cuda::__overrides_for; +}; + +using iset_ab = cuda::__iset, iprop_b<>, cuda::__imovable<>>; +using iset_a = cuda::__iset, cuda::__imovable<>>; +using iset_b = cuda::__iset, cuda::__imovable<>>; + +struct HasBoth +{ + _CCCL_HOST_DEVICE HasBoth(int v) + : val(v) + {} + _CCCL_HOST_DEVICE HasBoth(HasBoth&& o) noexcept + : val(o.val) + { + o.val = -1; + } + _CCCL_HOST_DEVICE HasBoth& operator=(HasBoth&& o) noexcept + { + val = o.val; + o.val = -1; + return *this; + } + _CCCL_HOST_DEVICE int get_a() + { + return val; + } + _CCCL_HOST_DEVICE int get_b() + { + return val * 10; + } + int val; +}; + +#if !_CCCL_COMPILER(NVRTC) +_CCCL_HOST void test_iset_dynamic_cast() +{ + // Start with both interfaces, narrow to one, verify cross-cast to the other fails + cuda::__basic_any ab{::cuda::std::in_place_type, 7}; + assert(ab.has_value()); + + // Narrow to iset_a — the vtable still has iprop_b entries from the original + auto a = cuda::__dynamic_any_cast(::cuda::std::move(ab)); + assert(a.has_value()); + + // Widen back to iset_ab — should succeed because vtable was built with both + auto ab2 = cuda::__dynamic_any_cast(::cuda::std::move(a)); + assert(ab2.has_value()); + assert(cuda::__any_cast(&ab2)->val == 7); + + // Now start with only iset_a — vtable has no iprop_b entries + cuda::__basic_any a_only{::cuda::std::in_place_type, 42}; + assert(a_only.has_value()); + +# if _CCCL_HAS_EXCEPTIONS() + // Cross-cast to iset_ab should fail — iprop_b is not in the vtable + try + { + auto bad = cuda::__dynamic_any_cast(::cuda::std::move(a_only)); + (void) bad; + assert(false && "should have thrown"); + } + catch (cuda::__bad_any_cast const&) + { + // expected + } + + // Cross-cast to iset_b should also fail + cuda::__basic_any a_only2{::cuda::std::in_place_type, 42}; + try + { + auto bad = cuda::__dynamic_any_cast(::cuda::std::move(a_only2)); + (void) bad; + assert(false && "should have thrown"); + } + catch (cuda::__bad_any_cast const&) + { + // expected + } +# endif // _CCCL_HAS_EXCEPTIONS() +} +#endif // !_CCCL_COMPILER(NVRTC) + int main(int, char**) { NV_IF_TARGET(NV_IS_HOST, (test_basic_any(); test_basic_any();)) +#if !_CCCL_COMPILER(NVRTC) + NV_IF_TARGET(NV_IS_HOST, (test_iset_dynamic_cast();)) +#endif // !_CCCL_COMPILER(NVRTC) return 0; }