2 に答える
C ++ 11に準拠したGCCは、への最初の2つの呼び出しのタイプを推測できませんbar
。C ++ 11の拡張機能を実装しているため、警告が表示されます。
標準では、関数テンプレートの呼び出しの関数引数がa{ ... }
であり、パラメーターが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
}
この例では、最初の要素はOKであり、2番目の要素もタイプを生成するため2番目の要素もOKであり、2番目の要素は次のようint
に比較されます-この推論は何も推論しないため、制約を生成できません。したがって、最終的に2番目の呼び出しは次のようになります。。3番目はどの要素からも推測できないため、OKではありません。最後の呼び出しは、2つの要素に対して矛盾する控除をもたらします。{2}
T
T
int
T
これを機能させる1つの方法は、パラメータタイプなどのタイプを使用することです。
template <class T> void bar(std::initializer_list<std::initializer_list<T>> x) {
// ...
}
行うのは危険であることに注意する必要があります-中括弧の周りのstd::initializer_list<U>({...})
ものを削除する方がよいでしょう。(...)
あなたの場合、それは偶然に機能しますが、考慮してください
std::initializer_list<int> v({1, 2, 3});
// oops, now 'v' contains dangling pointers - the backing data array is dead!
その理由は({1, 2, 3})
、コピー/移動コンストラクターを呼び出して、に関連付けられinitializer_list<int>
た一時的なものを渡すためです。その後、その一時オブジェクトは破棄され、初期化が終了すると消滅します。リストに関連付けられている一時オブジェクトがなくなると、データを保持しているバックアップ配列も破棄されます(移動が省略された場合、「v」まで存続します。動作しないため、これは悪いことです。間違いなく悪い!)。ペアを省略することにより、はリストに直接関連付けられ、バッキング配列データは破棄された場合にのみ破棄されます。initializer_list<int>
{1, 2, 3}
v
v
リストの初期化は、どのタイプが初期化されているかを知ることに依存しています。{1}
多くのことを意味する可能性があります。に適用するint
と、1で埋められます。に適用すると、最初の要素にstd::vector<int>
1つの要素を作成することを意味します。等々。vector
1
タイプが完全に制約されていないテンプレート関数を呼び出すと、リストの初期化で使用できるタイプ情報がありません。また、タイプ情報がないと、リストの初期化は機能しません。
例えば:
bar({{0,1}});
あなたはこれがタイプであることを期待していますstd::initializer_list<std::pair<int,int>>
。しかし、コンパイラはどうやってそれを知ることができるのでしょうか?bar
の最初のパラメータは制約のないテンプレートです。文字通りどのタイプでもかまいません。コンパイラは、あなたがこの特定のタイプを意味しているとどのように推測できますか?
簡単に言えば、それはできません。コンパイラーは優れていますが、千里眼ではありません。リストの初期化は、型情報が存在する場合にのみ機能し、制約のないテンプレートはそのすべてを削除します。
C ++ 11によれば、すべての権利により、その行はコンパイルに失敗するはずでした。意図したタイプを推測できない{...}
ため、失敗するはずです。それはGCCのバグか何かのように見えます。