据我所知,问题不是由 引起的map::emplace
,而是由pair
构造函数引起的:
#include <map>
struct A
{
A(int) {}
A(A&&) = delete;
A(A const&) = delete;
};
int main()
{
std::pair<int, A> x(1, 4); // error
}
据我所知,这个代码示例无法编译,无论是使用 coliru 的 g++4.8.1 还是使用 clang++3.5,它们都使用 libstdc++。
问题的根源在于,虽然我们可以构造
A t(4);
也就是说std::is_constructible<A, int>::value == true
,我们不能将an隐式转换int
为A
[conv]/3
当且仅当声明格式正确时,表达式e
才能隐式转换为类型,对于某些发明的临时变量。T
T t=e;
t
注意复制初始化(=
)。这将创建一个临时文件并从此临时文件 [dcl.init]/17 进行A
初始化。t
这种来自临时的初始化尝试调用已删除的移动 ctor A
,这使得转换格式错误。
由于我们无法从 an 转换int
为 an ,因此期望调用A
的构造函数被 SFINAE 拒绝。这种行为令人惊讶,N4387 - 改进对和元组分析并尝试通过创建构造函数而不是拒绝它来改善这种情况。N4387 在 Lenexa 会议上被选为 C++1z。pair
explicit
下面介绍 C++11 规则。
[pairs.pair]/7-9 中描述了我期望调用的构造函数
template<class U, class V> constexpr pair(U&& x, V&& y);
7 要求: is_constructible<first_type, U&&>::value
是true
和
is_constructible<second_type, V&&>::value
是true
。
8 效果:构造函数首先用 初始化,std::forward<U>(x)
然后用
初始化std::forward<V>(y)
。
9 备注:如果U
不能隐式转换为
first_type
或V
不能隐式转换second_type
为此构造函数,则不应参与重载决议。
请注意“要求”部分和“备注”部分中的“不可隐式转换”之间is_constructible
的区别。调用此构造函数的要求已满足,但它可能不参与重载决议(= 必须通过 SFINAE 拒绝)。
因此,重载决策需要选择一个“更差的匹配”,即第二个参数为 a 的匹配A const&
。从参数创建一个临时int
变量并绑定到该引用,该引用用于初始化pair
数据成员 ( .second
)。初始化尝试调用 的已删除副本 ctor A
,并且该对的构造是非良构的。
libstdc++ 有(作为扩展)一些非标准的ctors。在最新的 doxygen中(以及 4.8.2 中),pair
我期望调用的构造函数(对标准要求的规则感到惊讶)是:
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)) { }
而实际调用的是非标准的:
// 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) { }
该程序根据标准格式不正确,它不仅被这个非标准的ctor拒绝。
最后,这里是is_constructible
和的规范is_convertible
。
is_constructible
[元.rel]/4
给定以下函数原型:
template <class T>
typename add_rvalue_reference<T>::type create();
is_constructible<T, Args...>
当且仅当以下变量定义对于某个发明的变量是格式良好的时,模板特化的谓词条件才会满足t
:
T t(create<Args>()...);
[注意:这些标记永远不会被解释为函数声明。—尾注] 访问检查就像在T
与Args
. 仅考虑变量初始化的直接上下文的有效性。
is_convertible
[meta.unary.prop]/6:
给定以下函数原型:
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.