-
Notifications
You must be signed in to change notification settings - Fork 367
Description
Description
Constructing a resource_ref from a type that:
- Publicly inherits from
cuda::mr::shared_resource<Impl>, AND - Has a constructor accepting
resource_ref
triggers a recursive constraint satisfaction error on GCC 14.3.0 with C++20:
error: satisfaction of atomic constraint '...' depends on itself
The same code compiles if:
- The derived type does NOT have a
resource_refconstructor parameter - The derived type is cast to its
shared_resource<Impl>base before constructingresource_ref
The Cycle
resource_ref{derived&} checks __satisfies<derived, ...>
-> __icopyable checks copyable<derived>
-> is_constructible<derived, derived&> considers derived(resource_ref)
-> checks if derived& converts to resource_ref
-> checks __satisfies<derived, ...> [CYCLE]
GCC evaluates the __icopyable interface for derived by checking copyable<derived>, which checks is_constructible<derived, derived&>. During overload resolution, GCC considers whether derived& can implicitly convert to resource_ref (via the derived(resource_ref) constructor), which requires checking __satisfies<derived, ...> — creating a cycle.
This is the same __satisfies that initiated the check, causing the recursive constraint error.
Minimal Reproducer
https://godbolt.org/z/ddfn1sE8Y (nvcc 13.2, --std=c++20 -arch=sm_75)
#include <cuda/memory_resource>
#include <cuda/stream_ref>
#include <cuda/std/cstddef>
struct my_resource_impl {
void* allocate(cuda::stream_ref, ::cuda::std::size_t, ::cuda::std::size_t) { return nullptr; }
void deallocate(cuda::stream_ref, void*, ::cuda::std::size_t, ::cuda::std::size_t) noexcept {}
void* allocate_sync(::cuda::std::size_t, ::cuda::std::size_t) { return nullptr; }
void deallocate_sync(void*, ::cuda::std::size_t, ::cuda::std::size_t) noexcept {}
bool operator==(my_resource_impl const&) const { return true; }
bool operator!=(my_resource_impl const&) const { return false; }
friend void get_property(my_resource_impl const&, cuda::mr::device_accessible) noexcept {}
};
using resource_ref = cuda::mr::resource_ref<cuda::mr::device_accessible>;
// A type that inherits from shared_resource AND has a constructor taking resource_ref.
struct derived_resource : public cuda::mr::shared_resource<my_resource_impl> {
using shared_base = cuda::mr::shared_resource<my_resource_impl>;
explicit derived_resource(resource_ref /*upstream*/)
: shared_base(cuda::std::in_place_type<my_resource_impl>) {}
friend void get_property(derived_resource const&, cuda::mr::device_accessible) noexcept {}
};
void test_fails() {
auto base_sr = cuda::mr::make_shared_resource<my_resource_impl>();
resource_ref ref{base_sr};
derived_resource dr{ref};
// ERROR: satisfaction of atomic constraint '...' depends on itself
ref = dr;
}
void test_workaround() {
auto base_sr = cuda::mr::make_shared_resource<my_resource_impl>();
resource_ref ref{base_sr};
derived_resource dr{ref};
// OK: casting to shared_resource base avoids the cycle
ref = static_cast<derived_resource::shared_base&>(dr);
}
int main() { return 0; }Context
This pattern (inheriting from shared_resource<Impl> and accepting resource_ref as an upstream resource) is used extensively in RMM for pool/arena memory resources and adaptor resources. We are currently working around this by casting to the shared_resource<Impl> base before constructing resource_ref, but this is fragile and affects user-facing API ergonomics.
Environment
- CCCL version: 3.3.0
- GCC version: 14.3.0
- C++ standard: C++20
- CUDA Toolkit: 13.1
Metadata
Metadata
Assignees
Labels
Type
Projects
Status