2011-10-08 11 views
9
#include <initializer_list> 
#include <utility> 

void foo(std::initializer_list<std::pair<int,int>>) {} 
template <class T> void bar(T) {} 

int main() { 
    foo({{0,1}}); //This works 
    foo({{0,1},{1,2}}); //This works 
    bar({{0,1}}); //This warns 
    bar({{0,1},{1,2}}); //This fails 
    bar(std::initializer_list<std::pair<int,int>>({{0,1},{1,2}})); //This works 
} 

これは、GCC 4.5.3でコンパイルされませんが、それはno matching function for call to ‘bar(<brace-enclosed initializer list>)’を言ってマークされた行のためのdeducing ‘T’ as ‘std::initializer_list<std::initializer_list<int> >’を言ってマークされた行に対する警告やエラーを与える初期化子リストの種類を推測しないでください。なぜgccはbarへの最初の呼び出しのタイプを推測することができますが、2番目の呼び出しの推測はできません。長くて醜いキャスティング以外にこれを修正する方法はありますか?テンプレートは常に

+0

[テンプレートがイニシャライザリストを受け入れないのはなぜですか](http://stackoverflow.com/questions/4757614/why-doesnt-my-template-accept-an-initializer-list) - その質問基本的には重複しています。型控除がイニシャライザリスト上で全く行われているという事実はg ++の拡張です。 – Omnifarious

+0

@これらが詐欺であるかどうかは不明です。私の質問は、明らかにこの質問よりも少ない詳細を求めることを意図していました。 –

答えて

18

GCCによるCCC 11 は、最初の2回の呼び出しのタイプを、barに推論することはできません。これは、の拡張子からC++ 11を実装しているため警告します。

標準は言うその関数テンプレートの呼び出しの関数引数が{ ... }であり、パラメータは、パラメータのタイプは、{...}によって推定することができないこと、(必要に応じて参照パラメータ)initializer_list<X>ない場合。パラメータのようなinitializer_list<X>である場合、イニシャライザリストの要素は、Xと比較することによって独立して導出され、各要素の控除が一致しなければなりません。この例では

template<typename T> 
void f(initializer_list<T>); 

int main() { 
    f({1, 2}); // OK 
    f({1, {2}}); // OK 
    f({{1}, {2}}); // NOT OK 
    f({1, 2.0}); // NOT OK 
} 

第1素子収率がintを入力し、2番目の要素はTに対して{2}を比較するため、最初はOKであり、かつ第二のもOKである - それはないので、この控除はconstradictionを得ることができません何かを推測すると、最終的に2番目の呼び出しにはTintとなります。 3番目の要素はいずれの要素でもTを推論できないため、OKではありません。最後の呼び出しでは、2つの要素の矛盾する控除が生成されます。より良い括弧の周りの人々(...)を削除する - この作品を作るために

一つの方法は、私がstd::initializer_list<U>({...})を行うことは危険であることに注意しなければならないパラメータの型として

template <class T> void bar(std::initializer_list<std::initializer_list<T>> x) { 
    // ... 
} 

を、このようなタイプを使用することです。あなたのケースでは、それは偶然で動作するように起こるが、

std::initializer_list<int> v({1, 2, 3}); 
// oops, now 'v' contains dangling pointers - the backing data array is dead! 

理由を考えてみ({1, 2, 3})はそれを{1, 2, 3}に関連した一時的なinitializer_list<int>を渡すinitializer_list<int>のコピー/移動のコンストラクタを呼び出すことです。その一時オブジェクトは、初期化が完了すると破棄されて終了します。リストに関連付けられた一時オブジェクトが消滅すると、データを保持するバックアップ配列も破棄されます(移動が省略された場合、 "v"の長さで存続します;それは動作しないために悪い悪いことに保証!)。括弧を省略すると、vがリストに直接関連付けられ、vが破棄された場合にのみ、バッキングアレイデータが破棄されます。

+0

私は最後の段落を理解していませんでした。もう少し詳しく説明できますか? 'std :: initializer_list v({1,2,3});'と 'std :: initializer_list v {1、2、3};'の間に違いは見られません。彼らは私と同じように見えます。 – Nawaz

+0

@Nawaz私は私ができることのほとんどをそれについて精緻化したと思った。あなたはまだ質問がある場合、新しい質問を開くことをお勧めします。必要な細部が新しい答えを保証するからです。 –

+0

@Nawaz最後の段落について新しい質問をしましたか?コメントでここを転送することができます。 – SebNag

4

一様初期化は、どのタイプが初期化されているかを知ることに依存しています。 {1}は多くのことを意味する可能性があります。 intに適用すると、それは1で塗りつぶされます。std::vector<int>に適用すると、1つの要素vectorを作成し、最初の要素には1となります。等々。

タイプが完全に制約されていないテンプレート関数を呼び出すと、均一な初期化を行うための型情報はありません。タイプ情報がなければ、一様な初期化は機能しません。例えば

bar({{0,1}}); 

あなたがこのタイプstd::initializer_list<std::pair<int,int>>のことを期待しています。しかし、どのようにコンパイラがそれを知ることができましたかbarの最初のパラメータは無制約テンプレートです。それは文字通り任意のタイプにすることができます。コンパイラはおそらくこの特定の型を意味すると推測できますか?

まったく簡単にはできません。コンパイラは良いですが、彼らは透視的ではありません。統一された初期化は型情報の存在下でのみ機能し、制約のないテンプレートはそれをすべて削除します。

C++ 11によると、その行はコンパイルに失敗したはずです。 {...}の意図したタイプを推測することはできないため、失敗しているはずです。それはGCCのバグのように見えます。

+0

コンパイラは、 'std :: pair 'と' {1、2} 'のようになります。私はあなたの答えの最初の部分でどのように問題を記述するのが好きです。同じ意味合いが 'pair 'の 'T'を得る方法に適用されます:) –

+0

@ JohannesSchaub-litb:十分に公正です。削除されました。 –

関連する問題