问题
The following code will not compile on gcc 4.8.2.
The problem is that this code will attempt to copy construct an std::pair<int, A>
which can't happen due to struct A
missing copy and move constructors.
Is gcc failing here or am I missing something?
#include <map>
struct A
{
int bla;
A(int blub):bla(blub){}
A(A&&) = delete;
A(const A&) = delete;
A& operator=(A&&) = delete;
A& operator=(const A&) = delete;
};
int main()
{
std::map<int, A> map;
map.emplace(1, 2); // doesn't work
map.emplace(std::piecewise_construct,
std::forward_as_tuple(1),
std::forward_as_tuple(2)
); // works like a charm
return 0;
}
回答1:
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
andis_constructible<second_type, V&&>::value
istrue
.8 Effects: The constructor initializes first with
std::forward<U>(x)
and second withstd::forward<V>(y)
.9 Remarks: If
U
is not implicitly convertible tofirst_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.
来源:https://stackoverflow.com/questions/21405674/why-do-i-need-to-use-piecewise-construct-in-mapemplace-for-single-arg-construc