据我所知,问题不是由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,我们不能隐式转换 int 到 A [conv]/3
表达式 e 可以隐式转换为类型 T 当且仅当声明 T t=e; 格式正确,
对于一些发明的临时变量t。
注意复制初始化(=)。这会创建一个临时的 A 并从这个临时的 [dcl.init]/17 初始化 t。这种临时初始化尝试调用 A 的已删除移动 ctor,这使得转换格式不正确。
由于我们无法将 int 转换为 A,因此我们希望调用的 pair 的构造函数被 SFINAE 拒绝。这种行为令人惊讶,N4387 - Improving pair and tuple 分析并尝试通过使构造函数 explicit 而不是拒绝它来改善这种情况。 N4387 在 Lenexa 会议上被选为 C++1z。
下面介绍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 this
构造函数不应参与重载决议。
注意 Requires 部分中的 is_constructible 与 Remarks 部分中的“不可隐式转换”之间的区别。满足调用此构造函数的要求,但它可能不参与重载决议(= 必须通过 SFINAE 拒绝)。
因此,重载解析需要选择一个“更差的匹配”,即第二个参数是A const&。从int 参数创建一个临时对象并绑定到该引用,该引用用于初始化pair 数据成员(.second)。初始化尝试调用 A 的已删除副本 ctor,并且该对的构造不正确。
libstdc++ 有(作为扩展)一些非标准的ctors。在latest 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[meta.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();
模板特化is_convertible<From, To>的谓词条件必须满足,如果并且
仅当以下代码中的返回表达式格式正确时,包括任何隐式转换
到函数的返回类型:
To test() {
return create<From>();
}
[注意:此要求为引用类型、void 类型、数组类型和函数类型提供了明确定义的结果。 — 尾注] 访问检查就像在与To 和From 无关的上下文中执行。仅有的
return-statement 表达式的直接上下文的有效性(包括转换为
返回类型)被考虑。
对于您的类型A,
A t(create<int>());
格式正确;然而
A test() {
return create<int>();
}
创建一个A 类型的临时变量,并尝试将其移动 到返回值中(复制初始化)。这选择了已删除的 ctor A(A&&),因此格式错误。