Add special construct for std::pair in Allocator

Summary:
This is unfortunately needed in the C++17 standard, so that the
allocator is correctly propagated to elements of pair which respect the
"Uses Allocator" protocol. C++20 standard resolves this issue, but we
still have a long way before it is released and implemented by the
compiler and standard library vendors.

Reviewers: mtomic, llugovic, mferencevic

Reviewed By: mtomic

Subscribers: pullbot

Differential Revision: https://phabricator.memgraph.io/D2107
This commit is contained in:
Teon Banek 2019-06-03 16:22:08 +02:00
parent 716a80edf0
commit 16555eeb9a
2 changed files with 97 additions and 6 deletions
src/utils
tests/unit

View File

@ -6,7 +6,9 @@
#include <cstddef>
#include <memory>
#include <tuple>
#include <type_traits>
#include <utility>
// Although <memory_resource> is in C++17, gcc libstdc++ still needs to
// implement it fully. It should be available in the next major release
// version, i.e. gcc 9.x.
@ -101,11 +103,22 @@ class Allocator {
template <class U>
Allocator &operator=(const Allocator<U> &) = delete;
MemoryResource *GetMemoryResource() const { return memory_; }
T *allocate(size_t count_elements) {
return static_cast<T *>(
memory_->Allocate(count_elements * sizeof(T), alignof(T)));
}
void deallocate(T *p, size_t count_elements) {
memory_->Deallocate(p, count_elements * sizeof(T), alignof(T));
}
/// Return default NewDeleteResource() allocator.
Allocator select_on_container_copy_construction() const {
return utils::NewDeleteResource();
}
template <class U, class... TArgs>
void construct(U *ptr, TArgs &&... args) {
if constexpr (std::uses_allocator_v<U, Allocator>) {
@ -127,19 +140,72 @@ class Allocator {
}
}
void deallocate(T *p, size_t count_elements) {
memory_->Deallocate(p, count_elements * sizeof(T), alignof(T));
// Overloads for constructing a std::pair. Needed until C++20, when allocator
// propagation to std::pair in std::map is resolved. These are all modeled
// after std::pmr::polymorphic_allocator<>::construct, documentation
// referenced here:
// https://en.cppreference.com/w/cpp/memory/polymorphic_allocator/construct
template <class T1, class T2, class... Args1, class... Args2>
void construct(std::pair<T1, T2> *p, std::piecewise_construct_t,
std::tuple<Args1...> x, std::tuple<Args2...> y) {
auto xprime = MakePairElementArguments<T1>(&x);
auto yprime = MakePairElementArguments<T2>(&y);
::new (p) std::pair<T1, T2>(std::piecewise_construct, std::move(xprime),
std::move(yprime));
}
/// Return default NewDeleteResource() allocator.
Allocator select_on_container_copy_construction() const {
return utils::NewDeleteResource();
template <class T1, class T2>
void construct(std::pair<T1, T2> *p) {
construct(p, std::piecewise_construct, std::tuple<>(), std::tuple<>());
}
MemoryResource *GetMemoryResource() const { return memory_; }
template <class T1, class T2, class U, class V>
void construct(std::pair<T1, T2> *p, U &&x, V &&y) {
construct(p, std::piecewise_construct,
std::forward_as_tuple(std::forward<U>(x)),
std::forward_as_tuple(std::forward<V>(y)));
}
template <class T1, class T2, class U, class V>
void construct(std::pair<T1, T2> *p, const std::pair<U, V> &xy) {
construct(p, std::piecewise_construct, std::forward_as_tuple(xy.first),
std::forward_as_tuple(xy.second));
}
template <class T1, class T2, class U, class V>
void construct(std::pair<T1, T2> *p, std::pair<U, V> &&xy) {
construct(p, std::piecewise_construct,
std::forward_as_tuple(std::forward<U>(xy.first)),
std::forward_as_tuple(std::forward<V>(xy.second)));
}
private:
MemoryResource *memory_;
template <class TElem, class... TArgs>
auto MakePairElementArguments(std::tuple<TArgs...> *args) {
if constexpr (std::uses_allocator_v<TElem, Allocator>) {
if constexpr (std::is_constructible_v<TElem, std::allocator_arg_t,
MemoryResource *, TArgs...>) {
return std::tuple_cat(std::make_tuple(std::allocator_arg, memory_),
std::move(*args));
} else if constexpr (std::is_constructible_v<TElem, TArgs...,
MemoryResource *>) {
return std::tuple_cat(std::move(*args), std::make_tuple(memory_));
} else {
static_assert(!std::uses_allocator_v<TElem, Allocator>,
"Class declares std::uses_allocator but has no valid "
"constructor overload. Refer to 'Uses-allocator "
"construction' rules in C++ reference.");
}
} else {
// Explicitly do a move as we don't want a needless copy of `*args`.
// Previous return statements return a temporary, so the compiler should
// optimize that.
return std::move(*args);
}
}
};
template <class T, class U>

View File

@ -205,3 +205,28 @@ TYPED_TEST(AllocatorTest, PropagatesToStdUsesAllocator) {
EXPECT_EQ(c.value_, 42);
EXPECT_EQ(c.memory_, utils::NewDeleteResource());
}
TYPED_TEST(AllocatorTest, PropagatesToStdPairUsesAllocator) {
{
std::vector<std::pair<ContainerWithAllocatorFirst, TypeParam>,
utils::Allocator<TypeParam>>
vec(utils::NewDeleteResource());
vec.emplace_back(1, 2);
const auto &pair = vec.front();
EXPECT_EQ(pair.first.value_, 1);
EXPECT_EQ(pair.second.value_, 2);
EXPECT_EQ(pair.first.memory_, utils::NewDeleteResource());
EXPECT_EQ(pair.second.memory_, utils::NewDeleteResource());
}
{
std::vector<std::pair<ContainerWithAllocatorLast, TypeParam>,
utils::Allocator<TypeParam>>
vec(utils::NewDeleteResource());
vec.emplace_back(1, 2);
const auto &pair = vec.front();
EXPECT_EQ(pair.first.value_, 1);
EXPECT_EQ(pair.second.value_, 2);
EXPECT_EQ(pair.first.memory_, utils::NewDeleteResource());
EXPECT_EQ(pair.second.memory_, utils::NewDeleteResource());
}
}