28

以下代码无法在 gcc 4.8.2 上编译。问题是此代码将尝试复制由于缺少复制和移动构造函数std::pair<int, A>而无法发生的构造。struct A

gcc 在这里失败还是我错过了什么?

#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;
}
4

1 回答 1

23

据我所知,问题不是由 引起的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隐式转换intA[conv]/3

当且仅当声明格式正确时,表达式e才能隐式转换为类型,对于某些发明的临时变量。TT t=e;t

注意复制初始化(=)。这将创建一个临时文件并从此临时文件 [dcl.init]/17 进行A初始化。t这种来自临时的初始化尝试调用已删除的移动 ctor A,这使得转换格式错误。


由于我们无法从 an 转换int为 an ,因此期望调用A的构造函数被 SFINAE 拒绝。这种行为令人惊讶,N4387 - 改进对和元组分析并尝试通过创建构造函数而不是拒绝它来改善这种情况。N4387 在 Lenexa 会议上被选为 C++1z。pairexplicit

下面介绍 C++11 规则。

[pairs.pair]/7-9 中描述了我期望调用的构造函数

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

7    要求: is_constructible<first_type, U&&>::valuetrueis_constructible<second_type, V&&>::valuetrue

8    效果:构造函数首先用 初始化,std::forward<U>(x)然后用 初始化std::forward<V>(y)

9    备注:如果U不能隐式转换为 first_typeV不能隐式转换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>()...);

[注意:这些标记永远不会被解释为函数声明。—尾注] 访问检查就像在TArgs. 仅考虑变量初始化的直接上下文的有效性。

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.

于 2014-01-28T13:35:22.880 回答