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 typeT
if and only if the declarationT t=e;
is well-formed,
for some invented temporary variablet
.
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
istrue
and
is_constructible<second_type, V&&>::value
istrue
.8 Effects: The
constructor initializes first withstd::forward<U>(x)
and second with
std::forward<V>(y)
.9 Remarks: If
U
is not implicitly convertible to
first_type
orV
is not implicitly convertible tosecond_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 variablet
: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 theArgs
. 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
andFrom
. 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 tuple
s for both types, but instead I'd always get two int
s 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 overload 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 movable 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::vector
s.
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::vector
s, 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 offirst
and forwards the elements ofsecond_args
to the constructor ofsecond
. This is the only non-default constructor that can be used to create a pair of non-copyable non-movable types.
Related Topics
How Can Boost::Serialization Be Used with Std::Shared_Ptr from C++11
How to Iterate Backwards Through an Stl List
In C/C++ Why Does the Do While(Expression); Need a Semi Colon
How to Link Libcurl to My C++ Program in Linux
Resolving a Circular Dependency Between Template Classes
Inet_Pton': Identifier Not Found
Is Shrink_To_Fit the Proper Way of Reducing the Capacity a 'Std::Vector' to Its Size
Two Dimensional Array Using Vector
Creating a String List and an Enum List from a C++ MACro
Why Define Operator + or += Outside a Class, and How to Do It Properly
How to Asynchronously Copy Memory from the Host to the Device Using Thrust and Cuda Streams
Virtual Inheritance: Error: No Unique Final Overrider
Clear Data Inside Text File in C++
Win32 - Get Main Wnd Handle of Application
Filesystem' with C++17 Doesn't Work on My MAC Os X High Sierra
Qthread Emits Finished() Signal But Isrunning() Returns True and Isfinished() Returns False