https://gcc.gnu.org/bugzilla/show_bug.cgi?id=64096
Bug ID: 64096
Summary: std::list, set and map violate a rule about
allocator::construct
Product: gcc
Version: 4.9.0
Status: UNCONFIRMED
Severity: normal
Priority: P3
Component: libstdc++
Assignee: unassigned at gcc dot gnu.org
Reporter: palpatin91 at mail dot ru
According to the 23.2.1p3 C++11 Standard:
===
For the components affected by this subclause that declare an allocator_type,
objects stored in these components shall be constructed using the
allocator_traits::construct function and destroyed using the
allocator_traits::destroy function (20.6.8.2). These functions
are called only for the container’s element type, not for internal types used
by the container. [ Note: This means, for example, that a node-based container
might need to construct nodes containing aligned buffers and call construct to
place the element into the buffer. —end note ]
===
Here allocator_type is a direct template's type argument, and allocator_traits
just call allocator's construct method, if it exists. However, std::list, set
and map (and also multiset and multimap) violate this rule and call construct
method from an allocator of rebinded type, not of an original one. I know, that
we need rebinding for memory allocation, but for construct call we should use
original one. 23.2.1p13 also proves this:
===
Given a container type X having an allocator_type identical to A and a
value_type identical to T and given an lvalue m of type A, a pointer p of type
T*, an expression v of type T, and an rvalue rv of type T, the following terms
are defined. (If X is not allocator-aware, the terms below are defined as if A
were std::allocator.)
— T is CopyInsertable into X means that the following expression is
well-formed: allocator_traits::construct(m, p, v);
— T is MoveInsertable into X means that the following expression is
well-formed: allocator_traits::construct(m, p, rv);
— T is EmplaceConstructible into X from args, for zero or more arguments args,
means that the following expression is well-formed:
allocator_traits::construct(m, p, args);
===
Now some code that confirms a bug:
===
#include
#include
#include
#include
#include
#include
#include
template
struct my_allocator : public std::allocator {
my_allocator() noexcept {}
template
my_allocator(const my_allocator& source) noexcept
: std::allocator(source) {}
template
struct rebind {
using other = my_allocator;
};
template
void construct(U*, Args&&...) {
static_assert(!std::is_same::value, "Wrong construct");
}
};
template
bool operator==(const my_allocator&, const my_allocator&)
{ return true; }
template
bool operator!=(const my_allocator&, const my_allocator&)
{ return false; }
template<>
struct my_allocator : public std::allocator {
my_allocator() noexcept {}
template
my_allocator(const my_allocator& source) noexcept
: std::allocator(source) {}
template
struct rebind {
using other = my_allocator;
};
};
using map_int_int_value_type = std::map::value_type;
template<>
struct my_allocator
: public std::allocator {
my_allocator() noexcept {}
template
my_allocator(const my_allocator& source) noexcept
: std::allocator(source) {}
template
struct rebind {
using other = my_allocator;
};
};
int main()
{
{
using container = std::vector>;
container c;
c.emplace_back(0);
}
{
using container = std::list>;
container c;
c.emplace_back(0);
}
{
using container = std::set, my_allocator>;
container c;
c.emplace(0);
}
{
using container = std::multiset,
my_allocator>;
container c;
c.emplace(0);
}
{
using container = std::unordered_set,
std::equal_to, my_allocator>;
container c;
c.emplace(0);
}
{
using container = std::unordered_multiset,
std::equal_to, my_allocator>;
container c;
c.emplace(0);
}
{
using container = std::map,
my_allocator>;
container c;
c.emplace(0, 0);
}
{
using container = std::multimap,
my_allocator>;
container c;
c.emplace(0, 0);
}
{
using container = std::unordered_map,
std::equal_to, my_allocator>;
container c;
c.emplace(0, 0);
}
{
using container = std::unordered_mult