2

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.
mandrake
  • 1,213
  • 1
  • 14
  • 28
  • Well, `_Alloc` makes your program ill formed, no diagnostic required. Fixing that won't fix your problem. (Stop mimicing std header files, it uses mangled names because those mangled names are reserved for its use). No `_` followed by a capital letter in your code ever, or `__`. – Yakk - Adam Nevraumont May 22 '21 at 13:18
  • I kept same names to make it obvious what I was trying to show as it didn't make any difference on the result. I nevertheless corrected it. – mandrake May 22 '21 at 13:34

2 Answers2

1

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.

T.C.
  • 133,968
  • 17
  • 288
  • 421
  • To paraphrase for my own understanding: "There must be a valid constructor overload that accepts an allocator according to 'Uses-allocator construction'". The `emplace_back` requirement that is not fulfilled seems to be [_MoveInsertable_](https://en.cppreference.com/w/cpp/named_req/MoveInsertable). – mandrake May 23 '21 at 19:21
0

The first error message states that this isn't valid:

std::pmr::polymorphic_allocator<boost::circular_buffer<int, std::pmr::polymorphic_allocator<int> > > alice;

boost::circular_buffer<int, std::pmr::polymorphic_allocator<int> > bob(
  std::declval<boost::circular_buffer<int, std::pmr::polymorphic_allocator<int> >>(),
  alice
);

it appears to be trying to copy the circular buffer while passing in an allocator as the 2nd argument.

No overload of circular_buffer accepts these two arguments.

static_assert(std::is_constructible_v< Inner, Inner, Alloc<int>& >);

that is basically the assertion that is failing.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Isn't that the second error? The first error from `emplace_back` has `with _Args = {}` so does not attempt to perform any copy construction from what I can tell. Can subsequent errors be trusted to not be misleading given this? In particular since it seems to perform the ["Uses-allocator construction"](https://en.cppreference.com/w/cpp/memory/uses_allocator#Uses-allocator_construction)? – mandrake May 22 '21 at 14:32
  • I used the 4n1 godbolt, and decoded the first error message I saw. That error message appears to you can't construct itnusing a different allocator (a pmr to a buffer, as opposed to a pmr to an int). – Yakk - Adam Nevraumont May 22 '21 at 17:48