Why Do I Need to Use Piecewise_Construct in Map::Emplace for Single Arg Constructors of Noncopyable Objects

why do i need to use piecewise_construct in map::emplace for single arg constructors of noncopyable objects?

As far as I can tell, the issue isn't caused by map::emplace, but by pair's constructors:

#include <map>

struct A
{
A(int) {}
A(A&&) = delete;
A(A const&) = delete;
};

int main()
{
std::pair<int, A> x(1, 4); // error
}

This code example doesn't compile, neither with coliru's g++4.8.1 nor with clang++3.5, which are both using libstdc++, as far as I can tell.

The issue is rooted in the fact that although we can construct

A t(4);

that is, std::is_constructible<A, int>::value == true, we cannot implicitly convert an int to an A [conv]/3

An expression e can be implicitly converted to a type T if and only if the declaration T t=e; is well-formed,
for some invented temporary variable t.

Note the copy-initialization (the =). This creates a temporary A and initializes t from this temporary, [dcl.init]/17. This initialization from a temporary tries to call the deleted move ctor of A, which makes the conversion ill-formed.


As we cannot convert from an int to an A, the constructor of pair that one would expect to be called is rejected by SFINAE. This behaviour is surprising, N4387 - Improving pair and tuple analyses and tries to improve the situation, by making the constructor explicit instead of rejecting it. N4387 has been voted into C++1z at the Lenexa meeting.

The following describes the C++11 rules.

The constructor I had expected to be called is described in [pairs.pair]/7-9

template<class U, class V> constexpr pair(U&& x, V&& y);

7    Requires: is_constructible<first_type, U&&>::value is true and
is_constructible<second_type, V&&>::value is true.

8    Effects: The
constructor initializes first with std::forward<U>(x) and second with
std::forward<V>(y).

9    Remarks: If U is not implicitly convertible to
first_type or V is not implicitly convertible to second_type this
constructor shall not participate in overload resolution.

Note the difference between is_constructible in the Requires section, and "is not implicitly convertible" in the Remarks section. The requirements are fulfilled to call this constructor, but it may not participate in overload resolution (= has to be rejected via SFINAE).

Therefore, overload resolution needs to select a "worse match", namely one whose second parameter is a A const&. A temporary is created from the int argument and bound to this reference, and the reference is used to initialize the pair data member (.second). The initialization tries to call the deleted copy ctor of A, and the construction of the pair is ill-formed.


libstdc++ has (as an extension) some nonstandard ctors. In the latest doxygen (and in 4.8.2), the constructor of pair that I had expected to be called (being surprised by the rules required by the Standard) is:

template<class _U1, class _U2,
class = typename enable_if<__and_<is_convertible<_U1, _T1>,
is_convertible<_U2, _T2>
>::value
>::type>
constexpr pair(_U1&& __x, _U2&& __y)
: first(std::forward<_U1>(__x)), second(std::forward<_U2>(__y)) { }

and the one that is actually called is the non-standard:

// DR 811.
template<class _U1,
class = typename enable_if<is_convertible<_U1, _T1>::value>::type>
constexpr pair(_U1&& __x, const _T2& __y)
: first(std::forward<_U1>(__x)), second(__y) { }

The program is ill-formed according to the Standard, it is not merely rejected by this non-standard ctor.


As a final remark, here's the specification of is_constructible and is_convertible.

is_constructible [meta.rel]/4

Given the following function prototype:

template <class T>
typename add_rvalue_reference<T>::type create();

the predicate condition for a template specialization is_constructible<T, Args...> shall be satisfied if and only if the following variable definition would be well-formed for some invented variable t:

T t(create<Args>()...);

[Note: These tokens are never interpreted as a function declaration. — end note] Access checking is performed as if in a context unrelated to T and any of the Args. Only the validity of the immediate context of the variable initialization is considered.

is_convertible [meta.unary.prop]/6:

Given the following function prototype:

template <class T>
typename add_rvalue_reference<T>::type create();

the predicate condition for a template specialization is_convertible<From, To> shall be satisfied if and
only if the return expression in the following code would be well-formed, including any implicit conversions
to the return type of the function:

To test() {
return create<From>();
}

[Note: This requirement gives well defined results for reference types, void types, array types, and function types. — end note] Access checking is performed as if in a context unrelated to To and From. Only
the validity of the immediate context of the expression of the return-statement (including conversions to
the return type) is considered.


For your type A,

A t(create<int>());

is well-formed; however

A test() {
return create<int>();
}

creates a temporary of type A and tries to move that into the return-value (copy-initialization). That selects the deleted ctor A(A&&) and is therefore ill-formed.

Why do we need piecewise_construct in map emplace in C++20?

weirdo classes that take std::tuple as argument to constructor

Yes, exactly.

If you decided that m.emplace(x, y) automatically did piecewise_construct functionality for tuples x and y, then it would be impossible for me to emplace construct this:

struct Key { Key(std::tuple<int, int>); bool operator<(Key const&) const; };
struct Value { Value(std::tuple<char>); };
map<Key, Value> m;
m.emplace(std::tuple(1, 2), std::tuple('3'));

I need to provide tuples for both types, but instead I'd always get two ints for Key and a char for Value and that doesn't work. I would necessarily have to either add an extra constructor for Key or just not do any emplacing.

Now, if you follow up and say, okay, m.emplace(x, y) will automatically do piecewise_construct if x and y are tuples and also the piecewise construction is actually valid... then , first, that's getting fairly complicated. But also, what if both constructions are valid?

struct Key2 {
Key2(std::tuple<int, int>);
Key2(int, int);
bool operator<(Key2 const&) const;
};
map<Key2, Value> m2;
m2.emplace(std::tuple(1, 2), std::tuple('3'));

Now this always uses the (int, int) constructor. Is that what the user intended here?

Basically, having them separate means the user can do what they need and there's no ambiguity.

C++11 use-case for piecewise_construct of pair and tuple?

Not all types can be moved more efficiently than copied, and for some types it may make sense to even explicitly disable both copying and moving. Consider std::array<int, BIGNUM> as an an example of the former kind of a type.

The point with the emplace functions and piecewise_construct is that such a class can be constructed in place, without needing to create temporary instances to be moved or copied.

struct big {
int data[100];
big(int first, int second) : data{first, second} {
// the rest of the array is presumably filled somehow as well
}
};

std::pair<big, big> pair(piecewise_construct, {1,2}, {3,4});

Compare the above to pair(big(1,2), big(3,4)) where two temporary big objects would have to be created and then copied - and moving does not help here at all! Similarly:

std::vector<big> vec;
vec.emplace_back(1,2);

The main use case for piecewise constructing a pair is emplacing elements into a map or an unordered_map:

std::map<int, big> map;
map.emplace(std::piecewise_construct, /*key*/1, /*value*/{2,3});

Emplace a std::array of non-movable objects that cannot be default constructed

With custom array, you might do

template <typename T, std::size_t N>
struct MyArray
{
template <typename... Us>
MyArray(Us&&... args) : arr{ std::forward<Us>(args)...} {}

std::array<T, N> arr;
};

void foo()
{
std::map<int, MyArray<MyObject, 3>> my_map;
my_map.emplace(std::piecewise_construct,
std::forward_as_tuple(4),
std::forward_as_tuple(std::string("string0"),
std::string("string1"),
std::string("string2")));
}

Demo

How can a default-constructed object of a non-copyable class be emplaced into a boost::container::map?

Out of interest I played with this

As an important datapoint, I knew that std::map (in C++11) supports piecewise construction of it's value pairs:

std::map<int, A> stdMap;
stdMap.emplace(std::piecewise_construct,
std::forward_as_tuple(1),
std::forward_as_tuple());

would therefore invoke the contructor you're after. However, somehow, the same doesn't immediately work for Boost's map. Hmmm.

However, this piqued my interest: boost uses std::pair<const K, V> as the value type?!

boost::container::map<int, A>::value_type p { 
std::piecewise_construct,
std::forward_as_tuple(1),
std::forward_as_tuple(true)
};

works no problem. And I can also verify that this typedef is in fact the type stored:

static_assert(std::is_same<decltype(p), std::remove_reference<decltype(*myMap.begin())>::type>::value, "Nonstandard pair");

So, it's beginning to look like a bug in the forwarding via the interal tree implementation when it uses the allocator::construct call.

std::map<>::insert using non-copyable objects and uniform initialization

[This is a complete rewrite. My earlier answer had nothing to do with the problem.]

The map has two relevant insert overloads:

  • insert(const value_type& value), and

  • <template typename P> insert(P&& value).

When you use the simple list-initializer map.insert({1, non_copyable()});, all possible overloads are considered. But only the first one (the one taking const value_type&) is found, since the other doesn't make sense (there's no way to magically guess that you meant to create a pair). The first over­load doesn't work of course since your element isn't copyable.

You can make the second overload work by creating the pair explicitly, either with make_pair, as you already described, or by naming the value type explicitly:

typedef std::map<int, non_copyable> map_type;

map_type m;
m.insert(map_type::value_type({1, non_copyable()}));

Now the list-initializer knows to look for map_type::value_type constructors, finds the relevant mova­ble one, and the result is an rvalue pair which binds to the P&&-overload of the insert function.

(Another option is to use emplace() with piecewise_construct and forward_as_tuple, though that would get a lot more verbose.)

I suppose the moral here is that list-initializers look for viable overloads – but they have to know what to look for!

In place construction of a pair of nonmovable, non copyable in a std::vector

std::vector is subject to reallocation once the size reach the capacity.
when reallocating the elements into a new memory segment std::vector has to copy/move the values from the old segment and this is made by calling copy/move constructors.

if you don't need that the elements are sequential in memory you can use std::deque instead, since std::deque doesn't reallocate the elements internally.

you can't store non copyable and non moveable objects into std::vectors.


EDIT suggested by @François Andrieux

In case you still need for any reason an std::vector you may think to use a vector made using std::unique_ptr<X> as value type using std::vector<std::unique_ptr<X>>.

With this solution you still don't get a sequential order in memory of your elements, and they are keep in memory till they are still in the vector, so except in case you are forced by any reason to use std::vectors, i think the best match is still the std::deque.

Insert templated class in std::map with construction at insertion

You can use std::piecewise_construct and std::forward_as_tuple to create your objects in place.

class_a_map.emplace(
std::piecewise_construct,
std::forward_as_tuple(name),
std::forward_as_tuple(number1, number2)
);

live wandbox example


std::map::emplace perfectly forwards a bunch of arguments to the underlying std::pair used for key/value storage. std::pair::pair has an overload that takes an std::piecewise_construct_t as its first argument and then two std::tuple instances: the first one will be used to construct .first in place, the second one will be used to construct .second in place.

From cppreference, regarding std::pair's piecewise constructor:

Forwards the elements of first_args to the constructor of first and forwards the elements of second_args to the constructor of second. This is the only non-default constructor that can be used to create a pair of non-copyable non-movable types.



Related Topics



Leave a reply



Submit