スニペットを検討してください。
_#include <unordered_map>
void foo(const std::unordered_map<int,int> &) {}
int main()
{
foo({});
}
_
GCC 4.9.2では、次のメッセージが表示されて失敗します。
_map2.cpp:7:19: error: converting to ‘const std::unordered_map<int, int>’ from initializer list would use explicit constructor ‘std::unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::unordered_map(std::unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::size_type, const hasher&, const key_equal&, const allocator_type&) [with _Key = int; _Tp = int; _Hash = std::hash<int>; _Pred = std::equal_to<int>; _Alloc = std::allocator<std::pair<const int, int> >; std::unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::size_type = long unsigned int; std::unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::hasher = std::hash<int>; std::unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::key_equal = std::equal_to<int>; std::unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::allocator_type = std::allocator<std::pair<const int, int> >]’
_
他のコンパイラ/ライブラリ実装を使用したテスト:
その他の不可解な点:
std::unordered_map
_を_std::map
_に置き換えると、エラーがなくなります。foo({})
をfoo foo({{}})
に置き換えると、エラーもなくなります。また、_{}
_を空でない初期化子リストに置き換えることは、すべての場合に期待どおりに機能します。
私の主な質問は次のとおりです。
foo({{}})
を使用した構文は正確に何をしますか?[〜#〜] edit [〜#〜]いくつかのタイプミスを修正。
braced-init-listで使用している間接初期化構文は、copy-list-initializationと呼ばれます。
その場合に最適な実行可能なコンストラクタを選択するオーバーロード解決手順は、C++標準の次のセクションで説明されています。
§13.3.1.7リスト初期化による初期化_
[over.match.list]
_
非集約クラス型のオブジェクト
T
がリスト初期化される(8.5.4)場合、オーバーロード解決は2つのフェーズでコンストラクターを選択します。—最初、候補関数はクラス
T
の初期化子リストコンストラクター(8.5.4)であり、引数リストは単一引数としての初期化子リストで構成されます。—実行可能な初期化子リストコンストラクターが見つからない場合、オーバーロード解決が再度実行されます。ここで、候補関数はクラス
T
のすべてのコンストラクターであり、引数リストは初期化子リストの要素で構成されます。初期化子リストに要素がなく、
T
にデフォルトのコンストラクターがある場合、最初のフェーズは省略されます。 copy-list-initializationでは、明示的なコンストラクターが選択された場合、初期化の形式は正しくありません。 [Note:これは、変換コンストラクターのみがコピー初期化のために考慮される他の状況(13.3.1.3、13.3.1.4)とは異なります。この制限は、この初期化がオーバーロード解決の最終結果の一部である場合にのみ適用されます。 —終了ノート]。
それによると、initializer-list-constructor(タイプ_std::initializer_list<T>
_のコンストラクターのパラメーターに一致する単一の引数で呼び出し可能なもの)は通常、他のコンストラクターよりも優先されますただし、default-constructorが利用可能な場合、およびbraced-init-listはlist-initializationは空です。
ここで重要なことは、標準ライブラリのコンテナのコンストラクタのセットが、C++ 11とC++ 14の間で LWG問題2193 。 _std::unordered_map
_の場合、分析のために、次の違いに関心があります。
C++ 11:
_explicit unordered_map(size_type n = /* impl-defined */,
const hasher& hf = hasher(),
const key_equal& eql = key_equal(),
const allocator_type& alloc = allocator_type());
unordered_map(initializer_list<value_type> il,
size_type n = /* impl-defined */,
const hasher& hf = hasher(),
const key_equal& eql = key_equal(),
const allocator_type& alloc = allocator_type());
_
C++ 14:
_unordered_map();
explicit unordered_map(size_type n,
const hasher& hf = hasher(),
const key_equal& eql = key_equal(),
const allocator_type& alloc = allocator_type());
unordered_map(initializer_list<value_type> il,
size_type n = /* impl-defined */,
const hasher& hf = hasher(),
const key_equal& eql = key_equal(),
const allocator_type& alloc = allocator_type());
_
つまり、言語標準(C++ 11/C++ 14)に応じて、異なるデフォルトコンストラクター(引数なしで呼び出すことができるもの)があり、重要なのは、 C++ 14のデフォルトコンストラクターは、非explicit
になりました。
この変更は、次のように言えるように導入されました。
_std::unordered_map<int,int> m = {};
_
または:
_std::unordered_map<int,int> foo()
{
return {};
}
_
どちらも意味的にコードと同等です(_{}
_を初期化する関数呼び出しの引数として_std::unordered_map<int,int>
_を渡します)。
つまり、C++ 11準拠ライブラリの場合、エラーはexpectedであり、選択された(デフォルトの)コンストラクタはexplicit
、したがって、コードは不正な形式です。
_explicit unordered_map(size_type n = /* impl-defined */,
const hasher& hf = hasher(),
const key_equal& eql = key_equal(),
const allocator_type& alloc = allocator_type());
_
C++ 14準拠のライブラリの場合、エラーはnot expectedであり、選択された(デフォルト)コンストラクタはnotexplicit
、コードは整形式:
_unordered_map();
_
そのため、発生するさまざまな動作は、さまざまなコンパイラ/コンパイラオプションで使用しているlibstdc ++およびlibc ++のバージョンにのみ関連しています。
_
std::unordered_map
_を_std::map
_に置き換えると、エラーはなくなります。なぜ?
使用しているlibstdc ++バージョンの_std::map
_がすでにC++ 14用に更新されているためだと思われます。
foo({})
をfoo({{}})
に置き換えると、エラーもなくなります。なぜ?
現在、これはcopy-list-initialization _{{}}
_で、non-emptybraced- init-list(つまり、内部に1つの要素があり、空のbraced-init-list _{}
_で初期化されているため)§13.3の最初のフェーズからのルール。 initializer-list-constructorを他のものより優先する1.7 [over.match.list]/p1(前に引用)が適用されます。そのコンストラクタはexplicit
ではないため、呼び出しは整形式です。
_
{}
_を空でない初期化子リストに置き換えると、すべてのケースで期待どおりに機能します。なぜ?
上記と同じように、オーバーロードの解決は、13.3.1.7 [over.match.list]/p1の最初のフェーズで終わります。
参照のリスト初期化は、次のように定義されています[dcl.init.list]/3:
それ以外の場合、
T
が参照型の場合、T
によって参照される型の一時的なprvalueは、参照の初期化の種類に応じて、コピーリスト初期化または直接リスト初期化されます。 、および参照はその一時にバインドされます。
だからあなたのコードは失敗する
std::unordered_map<int,int> m = {};
失敗します。この場合のリストの初期化は、[dcl.init.list]/3の次の箇条書きで説明されています。
それ以外の場合、初期化リストに要素がなく、
T
がデフォルトのコンストラクターを持つクラス型である場合、オブジェクトは値で初期化されます。
そのため、オブジェクトのデフォルトのコンストラクタが呼び出されます1。
重要な部分:C++ 11では、unordered_map
にはこのデフォルトのコンストラクタがありました2:
explicit unordered_map(size_type n = /* some value */ ,
const hasher& hf = hasher(),
const key_equal& eql = key_equal(),
const allocator_type& a = allocator_type());
明らかに、copy_list-initializationを介してこのexplicit
コンストラクターを呼び出すのは不正な形式です[over.match.list]:
Copy-list-initializationでは、
explicit
コンストラクターが選択された場合、初期化の形式が正しくありません。
C++ 14 unordered_map
は、明示的でないデフォルトのコンストラクタを宣言します。
unordered_map();
したがって、C++ 14標準ライブラリの実装では、これを問題なくコンパイルする必要があります。おそらくlibc ++はすでに更新されていますが、libstdc ++は遅れています。
value-initializeタイプ
T
のオブジェクトの意味:
—T
が、ユーザー提供のコンストラクター(12.1)を持つ(おそらくcv修飾された)クラス型(9節)の場合、T
のデフォルトコンストラクターが呼び出されます[…];
2) [class.ctor]/4:
クラス
X
のデフォルトコンストラクターは、引数なしで呼び出すことができるクラスX
のコンストラクターです。