Search code examples
c++c++17c++pmrboost-circularbuffer

Using scoped `polymorphic_allocator` with `boost::circular_buffer` fails


Background

I want to use boost::circular_buffer with a scoped C++17 std::pmr::polymorphic_allocator. I.e. I want the same allocator for an outer container to be used for inner containers.

Side note:

boost::circular_buffer is allocator-aware and the following assertion is true:

static_assert(std::uses_allocator_v<
                boost::circular_buffer<int, std::pmr::polymorphic_allocator<int>>,
                std::pmr::polymorphic_allocator<int>>);

In this scenario I have a vector of circular buffers. With default allocator it would be of type std::vector<boost::circular_buffer<T>>

Using std::pmr::polymorphic_allocator I express it as:

#include <vector>
#include <memory_resource>
#include <boost/circular_buffer.hpp>

template<class T>
using Alloc = std::pmr::polymorphic_allocator<T>;
using Inner = boost::circular_buffer<int, Alloc<int>>;
using Outer = std::pmr::vector<Inner>;

Using these aliases, Inner works with std::pmr::polymorphic_allocator, but not as element of Outer.

The following works:

Outer::allocator_type alloc1; // Allocator type used by Outer to allocate Inner
Inner::allocator_type alloc2; // Allocator type used by Inner to allocate ints
// circular_buffer works if used directly with polymorphic_allocator
Inner inner1; // default arg OK
Inner inner2(alloc1); // pmr with allocator as last argument, also implicitly converts OK
Inner inner3(1, alloc2); // pmr with allocator as last argument OK

// Use to instantiate member functions
inner1.set_capacity(16);
inner2.set_capacity(16);
inner3.set_capacity(16);
inner1.push_back(1);
inner2.push_back(1);
inner3.push_back(1);

But when used as scoped allocator (which std::pmr::polymorphic_allocator supports without any need for std::scoped_allocator_adapter) it fails to compile with difficult to interpret errors.

One of the errors is a static_assertion failure because circular_buffer is not constructible using the provided allocator, which I'm reproducing (possibly incorrectly) but without triggering assertion here:

Outer v;
/* Statically asserts because Inner is not constructible

c++/12.0.0/bits/uses_allocator.h:98:60:error: static assertion failed: construction with an allocator must be possible if uses_allocator is true
98 |           is_constructible<_Tp, _Args..., const _Alloc&>>::value,

[with
_Args = {};
_Tp = boost::circular_buffer<int, std::pmr::polymorphic_allocator<int> >;
_Alloc = std::pmr::polymorphic_allocator<boost::circular_buffer<int, std::pmr::polymorphic_allocator<int> > >;
std::vector<_Tp, _Alloc>::reference = boost::circular_buffer<int, std::pmr::polymorphic_allocator<int> >&]
*/

// Note: Adding prefix `A` to make names allowed
using A_Tp = boost::circular_buffer<int, std::pmr::polymorphic_allocator<int> >;
using A_Alloc = std::pmr::polymorphic_allocator<boost::circular_buffer<int, std::pmr::polymorphic_allocator<int> > >;
static_assert(std::is_constructible<Inner, const A_Alloc&>::value); // OK
static_assert(std::is_same_v<Inner, A_Tp>); // OK
static_assert(std::is_same_v<typename Outer::allocator_type, A_Alloc>); // OK
v.emplace_back(); // ERROR
v.emplace_back(1); // ERROR 

Compiler explorer link

https://godbolt.org/z/4n1Gjhqxh

Questions

  1. Am I using std::pmr::polymorphic_allocator incorrectly here?
  2. What is the compiler trying to tell me with the error?
  3. Why is the reproduced assertion not failing as it is when used from emplace_back?
  4. Does std::pmr::polymorphic_allocator introduce any new/additional allocator requirements that make it incompatible with boost::circular_buffer or is this an issue with boost::circular_buffer?

Edit

  • Added prefix A in replication of STL template parameters.

Solution

  • The scoped allocator protocol requires that every constructor of the type has a corresponding allocator-extended version (either by appending an allocator parameter to the parameter list, or by prepending two parameters - allocator_arg_t followed by the allocator).

    That includes the copy and move constructors, for which boost::circular_buffer doesn't appear to provide allocator-extended versions. The requirement for these two allocator-extended constructors in particular is in the C++11 allocator-aware container requirements.

    For vector in particular, emplace_back needs to be able to copy or move existing elements on a reallocation, which is why it needs the allocator-extended copy/move constructor.