Skip to content

fmt::basic_memory_buffer<...,std::pmr::polymorphic_allocator> is not moveable #4487

@dlex

Description

@dlex

std::pmr::polymorphic_allocator is a non-propagating allocator: std::allocator_traits<std::pmr::polymorphic_allocator>::propagate_on_container_move_assignment::value is false and correspondingly, it has its move assignment operator deleted.

Since basic_memory_buffer::operator=(basic_memory_buffer&& other) unconditionally calls

fmt/include/fmt/format.h

Lines 829 to 830 in 40626af

FMT_CONSTEXPR20 void move(basic_memory_buffer& other) {
alloc_ = std::move(other.alloc_);

for both propagating and non-propagating allocators, a snippet like this won't compile:

    std::pmr::unsynchronized_pool_resource r1;
    using pmr_memory_buffer = fmt::basic_memory_buffer<char,10,std::pmr::polymorphic_allocator<char>>;
    pmr_memory_buffer b1{ &r1 };
    pmr_memory_buffer b2{ &r1 };
    ......
    b2 = std::move( b1 );

A bit more practical snippet can be found here: Compiler Explorer. It was inspired by the way fmt::memory_buffer class is used in spdlog. Its ringbuffer_sink declares a circular buffer of basically fmt::memory_buffers, and move()ing instances of fmt::memory_buffers into it when they are ready.

I'm considering a fix where the move-propagating allocators will behave as before, whereas those that don't propagate on move will check whether the allocators (and the memory resources) are the same for the source and for the target. If source and target allocator instances are the same, move() can proceed as usual, passing ownership of the underlying buffer. If the allocators are different, the buffer is then copied. That would replicate the behaviour of standard library containers.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions