web-dev-qa-db-ja.com

初期化子リストからの暗黙的な変換エラー

スニペットを検討してください。

_#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> >]’
_

他のコンパイラ/ライブラリ実装を使用したテスト:

  • GCC <4.9は文句を言わずにこれを受け入れますが、
  • Libstdc ++を使用したClang 3.5は、同様のメッセージで失敗します。
  • Libc ++を使用したClang 3.5はこれを受け入れますが、
  • ICC 15.somethingはこれを受け入れます(使用している標準ライブラリが不明です)。

その他の不可解な点:

  • _std::unordered_map_を_std::map_に置き換えると、エラーがなくなります。
  • foo({})をfoo foo({{}})に置き換えると、エラーもなくなります。

また、_{}_を空でない初期化子リストに置き換えることは、すべての場合に期待どおりに機能します。

私の主な質問は次のとおりです。

  • 誰がここにいますか?上記のコードは整形式ですか?
  • エラーをなくすために、二重中括弧foo({{}})を使用した構文は正確に何をしますか?

[〜#〜] edit [〜#〜]いくつかのタイプミスを修正。

41
bluescarni

braced-init-listで使用している間接初期化構文は、copy-list-initializationと呼ばれます。

その場合に最適な実行可能なコンストラクタを選択するオーバーロード解決手順は、C++標準の次のセクションで説明されています。

§13.3.1.7リスト初期化による初期化_[over.match.list]_

  1. 非集約クラス型のオブジェクト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-listlist-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の最初のフェーズで終わります。

36
Piotr Skotnicki

参照のリスト初期化は、次のように定義されています[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 ++は遅れています。


1)

value-initializeタイプTのオブジェクトの意味:
Tが、ユーザー提供のコンストラクター(12.1)を持つ(おそらくcv修飾された)クラス型(9節)の場合、Tのデフォルトコンストラクターが呼び出されます[…];

2) [class.ctor]/4:

クラスXのデフォルトコンストラクターは、引数なしで呼び出すことができるクラスXのコンストラクターです。

5
Columbo