72

次のコード:

#include <vector>

struct S
{
    int x, y;
};

int main()
{
    std::vector<S> v;
    v.emplace_back(0, 0);
}

GCC でコンパイルすると、次のエラーが発生します。

In file included from c++/4.7.0/i686-pc-linux-gnu/bits/c++allocator.h:34:0,
                 from c++/4.7.0/bits/allocator.h:48,
                 from c++/4.7.0/vector:62,
                 from test.cpp:1:
c++/4.7.0/ext/new_allocator.h: In instantiation of 'void __gnu_cxx::new_allocator<_Tp>::construct(_Up*, _Args&& ...) [with _Up = S; _Args = {int, int}; _Tp = S]':
c++/4.7.0/bits/alloc_traits.h:265:4:   required from 'static typename std::enable_if<std::allocator_traits<_Alloc>::__construct_helper<_Tp, _Args>::value, void>::type std::allocator_traits<_Alloc>::_S_construct(_Alloc&, _Tp*, _Args&& ...) [with _Tp = S; _Args = {int, int}; _Alloc = std::allocator<S>; typename std::enable_if<std::allocator_traits<_Alloc>::__construct_helper<_Tp, _Args>::value, void>::type = void]'
c++/4.7.0/bits/alloc_traits.h:402:4:   required from 'static void std::allocator_traits<_Alloc>::construct(_Alloc&, _Tp*, _Args&& ...) [with _Tp = S; _Args = {int, int}; _Alloc = std::allocator<S>]'
c++/4.7.0/bits/vector.tcc:97:6:   required from 'void std::vector<_Tp, _Alloc>::emplace_back(_Args&& ...) [with _Args = {int, int}; _Tp = S; _Alloc = std::allocator<S>]'
test.cpp:11:24:   required from here
c++/4.7.0/ext/new_allocator.h:110:4: error: new initializer expression list treated as compound expression [-fpermissive]
c++/4.7.0/ext/new_allocator.h:110:4: error: no matching function for call to 'S::S(int)'
c++/4.7.0/ext/new_allocator.h:110:4: note: candidates are:
test.cpp:3:8: note: S::S()
test.cpp:3:8: note:   candidate expects 0 arguments, 1 provided
test.cpp:3:8: note: constexpr S::S(const S&)
test.cpp:3:8: note:   no known conversion for argument 1 from 'int' to 'const S&'
test.cpp:3:8: note: constexpr S::S(S&&)
test.cpp:3:8: note:   no known conversion for argument 1 from 'int' to 'S&&'

vector引数から への要素を構築するために、通常()のコンストラクタ構文を使用していることを示唆していますemplace_back()。上記のような例を機能させるために、代わりに均一初期化構文をvector使用しないのはなぜですか?{}

使用することで失うものは何もないように思えます{}(コンストラクターがある場合はコンストラクターを呼び出しますが、コンストラクターがない場合でも機能します) {}。すべて、均一な初期化の要点は、オブジェクトを初期化するために均一に、つまりどこでも使用されることです。

4

1 に答える 1

59

偉大な頭脳は同じように考えます ;v) . 私は欠陥レポートを提出し、まさにこのトピックに関する標準の変更を提案しました。

http://cplusplus.github.com/LWG/lwg-active.html#2089

また、Luc Danton は、 std::allocator での Direct vs uniform initializationという難しさを理解するのに役立ちました。

EmplaceConstructible (23.2.1 [container.requirements.general]/13) 要件を使用してオブジェクトを初期化すると、直接初期化が発生します。集約を初期化するか、std::initializer_list コンストラクターを emplace で使用するには、初期化された型に名前を付け、一時的なものを移動する必要があります。これは、リスト初期化 (「均一な初期化」とも呼ばれる) 構文ではなく、直接初期化を使用した std::allocator::construct の結果です。

リスト初期化を使用するように std::allocator::construct を変更すると、特に std::initializer_list コンストラクターのオーバーロードが優先され、有効なコードが直観的で修正不可能な方法で壊れます — emplace_back がコンストラクターにアクセスする方法はありません。本質的に push_back を再実装せずに std::initializer_list によってプリエンプトされます。

std::vector<std::vector<int>> v;
v.emplace_back(3, 4); // v[0] == {4, 4, 4}, not {3, 4} as in list-initialization

提案された妥協案は、std::is_constructible で SFINAE を使用することです。これは、直接初期化が適切に形成されているかどうかをテストします。is_constructible が false の場合、リスト初期化を使用する代替 std::allocator::construct オーバーロードが選択されます。リスト初期化は常に直接初期化にフォールバックするため、直接初期化のオーバーロードは失敗しないため、リスト初期化 (均一初期化) が常に使用されているかのような診断メッセージがユーザーに表示されます。

このスキームのギャップを露呈する 2 つのコーナー ケースを見ることができます。1 つは、上記の例で {3, 4} の値を埋め込み挿入しようとするなど、std::initializer_list 用の引数がコンストラクタを満たす場合に発生します。回避策は、v.emplace_back(std::initializer_list(3, 4)) のように、std::initializer_list 型を明示的に指定することです。これはまるで std::initializer_list が推測されたかのようにセマンティクスに一致するため、実際には問題はないようです。

もう 1 つのケースは、集計の初期化を目的とした引数がコンストラクターを満たす場合です。集約にはユーザー定義のコンストラクターを含めることができないため、集約の最初の非静的データ メンバーが集約型から暗黙的に変換可能であり、初期化子リストに 1 つの要素が含まれている必要があります。回避策は、2 番目のメンバーの初期化子を指定することです。変換可能な型から集約自体の型に変換することによって、非静的データ メンバーが 1 つだけの集約をインプレースで構築することは依然として不可能です。これは許容できる小さな穴のようです。

于 2012-01-09T01:25:26.987 に答える