5

I am trying to write an allocator-aware container. Suppose I want to allocate a chunk of memory for three objects:

T* chunk = std::allocator_traits<Allocator>::allocate(allocator, 3);

(I know that allocators can have custom pointer types and therefore I should use std::allocator_traits<Allocator>::pointer; I am using raw pointers here for simplicity.)

Now I want to create an actual object at index 2. How do I do that? In particular, how do I calculate the pointer to the not-yet-existing element? The most obvious option would be the following:

std::allocator_traits<Allocator>::construct(allocator, chunk + 2, ...);

Unfortunately, chunk + 2 does not appear to be correct: according to the standard, pointer arithmetic can only be performed on pointers to array elements, and otherwise it causes undefined behavior. For the same reason, I cannot convert the pointer to std::byte* and use pointer arithmetic on that. (While std::allocator is defined to create an array in newly allocated memory, until C++20, the same requirement does not exist for custom allocators. Also, while C++20 adds some language for “implicit creation of objects”, this does not apply for earlier C++ versions.)

So how do I calculate the pointer to give as the second argument to construct without causing undefined behavior (before C++20)?

François Andrieux
  • 28,148
  • 6
  • 56
  • 87
voidf.0xb3
  • 53
  • 1
  • 4
  • Did you mean to tag it `language-lawyer` ? – Jeffrey Aug 17 '20 at 19:19
  • @Jeffrey I would think so, since the question already cites the standard. – cigien Aug 17 '20 at 19:25
  • You might be interested in [this thread](https://stackoverflow.com/q/62329008/2752075). Casting to `std::byte*` (or [`unsigned`] `char *`), adding offset, casting back, then `launder`ing is the safest option. (Even if ends up being formally UB, there's nothing you can do about it.) – HolyBlackCat Aug 17 '20 at 19:29
  • You are asking [`allocate()`](https://en.cppreference.com/w/cpp/memory/allocator_traits/allocate) to allocate storage for an array of 3 uninitialized `Allocator::value_type` objects, where `value_type` is `T`: "*Uses the allocator `a` to allocate `n*sizeof(Alloc::value_type)` bytes of uninitialized storage. **An array of type `Alloc::value_type[n]` is created in the storage**, but none of its elements is constructed.*" So why wouldn't pointer arithmetic be valid to use here? – Remy Lebeau Aug 17 '20 at 19:38
  • @RemyLebeau It looks liks the cppreference.com documentation contains information based on C++20. Here’s [a quote from the C++17 standard](https://timsong-cpp.github.io/cppwp/n4659/allocator.requirements): “Memory is allocated for n objects of type T but objects are not constructed.” Here’s [the corresponding part from the C++20 standard](https://timsong-cpp.github.io/cppwp/allocator.requirements): “Memory is allocated for an array of n T and such an object is created but array elements are not constructed.” So this would indeed work in C++20, but (it seems) not in C++17 or earlier. – voidf.0xb3 Aug 17 '20 at 19:47
  • @voidf.0xb3 OK, but it is still allocating an array of `n` elements either way, even if the elements themselves are not initialized yet, so pointer arithmetic over the array itself should be valid, in all versions. – Remy Lebeau Aug 17 '20 at 19:51
  • 1
    This is (was) a well-known problem of "non-implementability" of `std::vector`. See, for instance, [this post](https://stackoverflow.com/q/52996590/580083). – Daniel Langr Aug 17 '20 at 20:56
  • @RemyLebeau My understanding of the difference is that in C++17, only the storage for that array is allocated, but the array object itself is not created. – Daniel Langr Aug 18 '20 at 06:06

1 Answers1

3

In the latest standard draft (C++20):

[tab:cpp17.allocator]

a.allocate(n) - Memory is allocated for an array of n T and such an object is created but array elements are not constructed.

allocator_traits::allocate(n) simply calls a.allocate(n).

So, given that the array is created, the pointer arithmetic is well defined.


In C++17 before the acceptance of proposal P0593R6, the wording was:

Memory is allocated for n objects of type T is created but objects are not constructed.

Prior to this change there was no well-defined way to do what you are asking unless:

  • We assume that the custom allocator provides the guarantee that such array is created. Problem with this is that there is no standard way to create an array without creating the objects (without the default allocator) and thus no standard way to implement such custom allocator.
  • We ignore the restrictions of pointer arithmetic. Theoretical problem with this is the undefined behaviour. In practice, this has not been a problem with actual language implementations.
eerorika
  • 232,697
  • 12
  • 197
  • 326