2013-02-16 7 views
10

可変長引数をとることができるコンストラクタが必要な場合、可変テンプレート-またはstd::initializer_listのどちらを使用するかをC++ 11の現在の状態(gcc 4.7.2など)でどのように選択する必要がありますか?コンストラクタの場合、variadic-templatesとstd :: initializer_listのどちらを選択すればよいですか?

+3

私はわかりません(なぜこれがコメントなのですか)が、バリデーションテンプレートはさまざまなタイプを処理できませんが、イニシャライザリストはすべて同じタイプでなければなりませんか? –

+0

@JoachimPileborg、絶対に正しいですが、あなたは 'int ...'と 'std :: initializer_list 'の間で選択できます。私は使用するのが自然であると感じる方を選んで言う。 – chris

答えて

15

バリデーショナルテンプレートを使用すると、さまざまなタイプの引数を指定できますが、std::initializer_listには引数の型がテンプレート化されています。これは、リスト内のすべての要素の型が同じでなければならないことを意味します(または、基になる型に変換できますが、変換を狭めることはできません)。これが望ましいかどうかによって、どちらか一方を選択することができます。

同様のタイプの控除がinitializer_listのために実行することはできませんしながら、構文形式T&&は、左辺値参照と右辺値参照の両方に結合することができるという点で、あなたは、完璧な転送を必要とする場合にも、可変長引数テンプレートは通常、デフォルトの選択である:

struct A 
{ 
    // Deduces T& for lvalue references, T for rvalue references, and binds to both 
    template<typename... Ts> 
    A(Ts&&...) { } 

    // This is an rvalue reference to an initializer_list. The above type deduction 
    // does not apply here 
    template<typename T> 
    A(initializer_list<T>&&) { } 
}; 

はまた、あなたが均一な初期化構文(つまり、中括弧)を使用する場合initializer_listを受け入れるコンストラクタが別の実行可能なコンストラクタが存在していても、デフォルトで呼び出されることに、気づきます。

struct A 
{ 
    A(int i) { } 
}; 

struct B 
{ 
    B(int) { } 
    B(std::initializer_list<A>) { } 
}; 

int main() 
{ 
    B b {1}; // Will invoke the constructor accepting initializer_list 
} 
+3

完璧な転送に関するビットを拡張すると、 'std :: initializer_list :: reference'(' * l.begin() 'の結果型)は' T const 'で動きを禁止します。移動のみの値を入れることはできますが、移動することはできません。 –

+0

@LucDanton:良い点。 –

3

私は可変引数テンプレートを択一常にをお勧めしますし、可能な限りstd::initializer_listを避ける:これは、またはあなたが持っているしたい何かあってもなくてもよいです。

これは私がC++ 11とstd::vectorを実装しているだろうかです:、mainに示したような理由がある

#include <iostream> 
#include <vector> 

struct exp_sequence { 
    template <typename... T> 
    exp_sequence(T&&...) {} 
}; 

struct from_arglist_t {} from_arglist; 

template <typename T> 
class my_vector { 
    std::vector<T> data; 

public: 
    my_vector(int n, T const& e) : data(n, e) {} 

    template <typename... Args> 
    my_vector(from_arglist_t, Args&&... args) { 
    data.reserve(sizeof...(Args)); 
    exp_sequence{(data.push_back(std::forward<Args>(args)),1)...}; 
    } 

    std::size_t size() { return data.size(); } 
}; 

int main() 
{ 
    std::vector<int> v1{13, 13}; std::cout << v1.size() << '\n'; // 2 
    std::vector<int> v2(13, 13); std::cout << v2.size() << '\n'; // 13 

    my_vector<int> v3{13, 13}; std::cout << v3.size() << '\n'; // 13 
    my_vector<int> v4(13, 13); std::cout << v4.size() << '\n'; // 13 
    my_vector<int> v5(from_arglist, 13, 13); std::cout << v5.size() << '\n'; // 2 
    my_vector<int> v6{from_arglist, 13, 13}; std::cout << v6.size() << '\n'; // 2 
} 

汎用コードでinitializer_listを使用すると、括弧のタイプに応じて異なる動作をしてつながることができます選ばれたまた、このようなコンストラクタを追加することで、コードを静かに変更することも可能です。

もう一つの理由は、移動のみのタイプです:可変長引数テンプレートに

//std::vector<move_only> m1{move_only{}}; // won't compile 
my_vector<move_only> m2{from_arglist, move_only{}}; // works fine 
+0

'exp_sequence'トリックはきちんとしていますが、要素を正しい順序で挿入することは実際に保証されていますか? AFAIR、CおよびC++は、関数の引数を語彙順に評価することを決して約束しませんでした。したがって、可変的なテンプレートコンストラクタの引数評価順序についてC++ 11標準に特別な保証がない限り、これは移植性がありません。 – fgp

+1

@fgp:引数評価の順序は、実際には左から右に順序付けられることが保証されています。これは、中括弧の初期化を使用するたびに保持されます(したがって、exp_sequenceはクラスでなければなりません)。 – ipc

+1

@ipc 'exp_sequence {(data.push_back(std :: forward (args))、1)...}'で正確に何が起こっていますか? 'data.push_back(std :: forward (args))'を '('と '、1)'で囲む必要があるのはなぜですか?そしてなぜ「1」? – 0xbadf00d

4

、引数の数は、(sizeof...経由でアクセス可能)コンパイル時に知られています。 std::initializer_listでは、引数の数は実行時にのみ認識されます。だから決定の一部はあなたが必要とするか、あなたが持っている引数の数を知りたい時に依存します。

+0

コンテナは完全な状態で初期化する必要があるため、コンパイル時のサイズを持ちます(push_backなどはできません)。 ':: size()'メソッドが 'constexpr'とマークされていないということを意味するなら、これはC++ 11の不具合であり、C++ 14で修正されました。名前付き 'initializer_list'などのサイズをさまざまな形でテンプレート化することができます。サイズはコンパイル時に利用できます。しかし、確かに、多分variadicオプションと同じくらい多くの方法ではありません! –

+2

'initializer_list'のコンストラクタ_parameter_は、コンパイル時に知られているサイズを持っていません。なぜなら、各サイトで異なるサイズのイニシャライザを持つ複数のサイトからコンストラクタを呼び出すことができるからです。 – KnowItAllWannabe

+0

ええ、素晴らしい点、私は完全に前に考えていなかった:) –

関連する問題