copperspice / cs_libguarded

Header-only library for multithreaded programming
https://www.copperspice.com/
Other
226 stars 27 forks source link

Support construction with allocators for allocator-aware types #24

Open justusranvier opened 11 months ago

justusranvier commented 11 months ago

Add a constructor and type trait specialization which is active if T is allocator-aware

This allows a std::shared_ptr for a guarded type to be constructed with std::allocate_shared.

agserm commented 9 months ago

Thanks for submitting this, it does look like a useful feature. However, I looked at this recently and started to add some unit tests for the new functionality, and realized that something seems strange with the implementation of the uses_allocator specialization.

Can you give an example of a T where the uses_allocator trait is expected to be true? It would be good to have a use case.

justusranvier commented 9 months ago

The complication is that there's two ways to get the uses_allocator trait: the leading-allocator convention and the trailing-allocator convention.

This PR only works with types that use leading-allocator convention, in other words if the first argument of the constructor is std::allocator_arg.

Somebody who is better at TMP than me could probably adapt it to also work with trailing-allocator convention, but all the types I want to guard are leading-allocator and supporting both conventions would likely be a much more intrusive change. You might need to add new template parameters to every class.

justusranvier commented 9 months ago

Here's an example of a leading-allocator convention type, showing how it can be constructed with or without allocator construction:

struct TestType {
    using allocator_type = std::pmr::polymorphic_allocator<>;

    std::pmr::string example;

    TestType(allocator_type alloc = {}, std::string_view value = {})
        : example(value, alloc)
    {
    }
    TestType(
        std::allocator_arg_t,
        allocator_type alloc,
        std::string_view value = {})
        : TestType(alloc, value)
    {
    }
};

auto resource = std::pmr::monotonic_buffer_resource{};
auto alloc = std::pmr::polymorphic_allocator<>{&resource};
std::ignore = std::allocate_shared<TestType>(alloc);
std::ignore = TestType{};
justusranvier commented 9 months ago

The goal would be able to write

auto guarded_object = std::allocate_shared<libguarded::plain_guarded<TestTyoe>>{alloc};

And then pass the resulting std::shared_ptr to objects running in different threads.