web-dev-qa-db-ja.com

emplace_back()が均一な初期化を使用しないのはなぜですか?

次のコード:

_#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が代わりに_{}_の均一初期化構文を使用しないのはなぜですか?

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

63
HighCommander4

偉大な心は同じように考えます; v)。私は欠陥報告を提出し、まさにこのトピックに関する基準の変更を提案しました。

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

また、Luc Dantonは、私が難しさを理解するのを助けてくれました: std :: allocatorでの直接初期化と一様初期化

EmplaceConstructible(23.2.1 [container.requirements.general]/13)要件を使用してオブジェクトを初期化すると、直接初期化が発生します。集計を初期化するか、emplaceでstd :: initializer_listコンストラクターを使用するには、初期化された型に名前を付け、一時を移動する必要があります。これは、リスト初期化(「均一初期化」と呼ばれることもあります)構文ではなく、直接初期化を使用した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つは、std :: initializer_listを対象とした引数がコンストラクターを満たす場合に発生します。たとえば、上記の例では、値{3、4}を配置挿入しようとします。回避策は、v.emplace_back(std :: initializer_list(3、4))のように、std :: initializer_listタイプを明示的に指定することです。これは、std :: initializer_listが推定されたかのようにセマンティクスと一致するため、ここでは実際の問題はないようです。

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

51
Potatoswatter