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::emplacepair

#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);

つまり、 を暗黙的に [conv]/3 に変換することはstd::is_constructible<A, int>::value == trueできませんintA

ある発明された一時変数に対して、宣言が適切な形式である場合にのみ、式を暗黙的に型に変換できeます。TT t=e;t

コピーの初期化 ( =) に注意してください。これにより、テンポラリが作成され、このテンポラリ [dcl.init]/17 からA初期化されます。t一時的な からのこの初期化は、 の削除された move ctor を呼び出そうとAするため、変換が正しくありません。


intan から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&&>::value istrueおよび is_constructible<second_type, V&&>::valueis true.

8    効果:コンストラクターは最初に で初期化し、次に で初期化std::forward<U>(x)std::forward<V>(y)ます。

9    備考:U暗黙的に変換できない 場合、first_typeまたはこのコンストラクターVに暗黙的に変換できない場合、オーバーロードの解決に参加してはなりません。second_type

Requiresis_constructibleセクションの違いと、Remarksセクションの "is not implicitly convertible"の違いに注意してください。このコンストラクターを呼び出すための要件は満たされていますが、オーバーロードの解決に参加しない場合があります (= SFINAE を介して拒否する必要があります)。

したがって、オーバーロードの解決では、「より悪い一致」、つまり 2 番目のパラメーターが a のものを選択する必要がありますA const&。引数からテンポラリが作成され、intこの参照にバインドされます。参照は、pairデータ メンバ ( .second) の初期化に使用されます。初期化で、 の削除されたコピー ctor を呼び出そうとしましたAが、ペアの構成が正しくありません。


libstdc++ には (拡張機能として) いくつかの非標準の ctor があります。最新の 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();

テンプレートの特殊化の述語条件is_convertible<From, To>は、関数の戻り値の型への暗黙の変換を含め、次のコードの戻り値式が整形式である場合にのみ満たされます。

To test() {
  return create<From>();
}

[注:この要件により、参照型、void 型、配列型、および関数型について明確に定義された結果が得られます。—終わりの注To] アクセス チェックは、および とは無関係のコンテキストであるかのように実行されますFromreturn-statement (戻り型への変換を含む)の式の直接のコンテキストの有効性のみが考慮されます。


あなたのタイプについてAは、

A t(create<int>());

整形式です。でも

A test() {
  return create<int>();
}

タイプのテンポラリを作成し、それを戻り値に移動Aしようとします(コピー初期化)。これは削除されたctorを選択するため、形式が正しくありません。A(A&&)

于 2014-01-28T13:35:22.880 に答える