初期化子リストに対する可変個引数の利点は何でしょうか。どちらも同じ機能を提供します-関数に無数の引数を渡すことができます。
私が個人的に思うのは、初期化子リストはもう少しエレガントだと思います。構文はそれほど厄介ではありません。
また、引数の数が増えると、初期化子リストのパフォーマンスが大幅に向上するようです。
では、Cで可変個引数を使用する可能性に加えて、何が欠けているのでしょうか。
初期化子リストに対する可変個引数の利点は何でしょうか。どちらも同じ機能を提供します-関数に無数の引数を渡すことができます。
私が個人的に思うのは、初期化子リストはもう少しエレガントだと思います。構文はそれほど厄介ではありません。
また、引数の数が増えると、初期化子リストのパフォーマンスが大幅に向上するようです。
では、Cで可変個引数を使用する可能性に加えて、何が欠けているのでしょうか。
可変個引数引数によって(のように)省略記号を意味する場合、それらは初期化子リストではなく可変個引数テンプレートvoid foo(...)
によって多かれ少なかれ廃止されます-実装するためにSFINAEを使用する場合、省略記号のいくつかのユースケースがまだ存在する可能性があります(たとえば)タイプ特性、またはC互換性のためですが、ここでは通常のユースケースについて説明します。
実際、可変個引数テンプレートでは、引数パックにさまざまな型(実際には任意の型)を使用できますが、初期化子リストの値は、初期化子リストの基になる型に変換可能である必要があります(変換の絞り込みは許可されていません)。
#include <utility>
template<typename... Ts>
void foo(Ts...) { }
template<typename T>
void bar(std::initializer_list<T>) { }
int main()
{
foo("Hello World!", 3.14, 42); // OK
bar({"Hello World!", 3.14, 42}); // ERROR! Cannot deduce T
}
このため、引数の型が実際に同種であることが意図されていない限り、型の推定が必要な場合に初期化子リストが使用されることはあまりありません。一方、可変個引数テンプレートは、省略記号の可変個引数リストのタイプセーフバージョンを提供します。
また、初期化子リストを受け取る関数を呼び出すには、引数を中括弧のペアで囲む必要があります。これは、可変引数パックを受け取る関数の場合には当てはまりません。
最後に(他にも違いはありますが、これらはあなたの質問により関連性のあるものです)、初期化子リストの値はconst
オブジェクトです。C++11規格のパラグラフ18.9/1による:
タイプのオブジェクトは、タイプ
initializer_list<E>
のオブジェクトの配列へのアクセスを提供しますconst E
。[...]初期化子リストをコピーしても、基になる要素はコピーされません。[...]
つまり、コピーできない型は初期化子リストに移動できますが、初期化子リストから移動することはできません。この制限は、プログラムの要件を満たす場合と満たさない場合がありますが、通常、初期化子リストは、コピー不可能な型を保持するための制限的な選択肢になります。
より一般的には、とにかく、初期化子リストの要素としてオブジェクトを使用する場合、オブジェクトのコピーを作成するか(左辺値の場合)、オブジェクトから移動します(右辺値の場合)。
#include <utility>
#include <iostream>
struct X
{
X() { }
X(X const &x) { std::cout << "X(const&)" << std::endl; }
X(X&&) { std::cout << "X(X&&)" << std::endl; }
};
void foo(std::initializer_list<X> const& l) { }
int main()
{
X x, y, z, w;
foo({x, y, z, std::move(w)}); // Will print "X(X const&)" three times
// and "X(X&&)" once
}
言い換えると、初期化子リストを使用して、参照(*)で引数を渡すことはできません。ましてや、完全な転送を実行することもできません。
template<typename... Ts>
void bar(Ts&&... args)
{
std::cout << "bar(Ts&&...)" << std::endl;
// Possibly do perfect forwarding here and pass the
// arguments to another function...
}
int main()
{
X x, y, z, w;
bar(x, y, z, std::move(w)); // Will only print "bar(Ts&&...)"
}
(*)ただし、初期化子リスト(C ++標準ライブラリの他のすべてのコンテナーとは異なり)には参照セマンティクスがあるため、要素を初期化子リストに挿入するときに要素のコピー/移動が実行されますが、イニシャライザリスト自体は、含まれているオブジェクトのコピー/移動を引き起こしません(上記の標準の段落で述べたように):
int main()
{
X x, y, z, w;
auto l1 = {x, y, z, std::move(w)}; // Will print "X(X const&)" three times
// and "X(X&&)" once
auto l2 = l1; // Will print nothing
}
簡単に言うと、Cスタイルの可変個引数関数はC ++スタイルの可変個引数テンプレートよりもコンパイル時に生成されるコードが少ないため、バイナリサイズや命令キャッシュの負荷が心配な場合は、テンプレートとしてではなくvarargsを使用して機能を実装することを検討してください。
ただし、可変個引数テンプレートは非常に安全で、はるかに使いやすいエラーメッセージを生成するため、オフラインの可変個引数関数をインラインの可変個引数テンプレートでラップし、ユーザーにテンプレートを呼び出させることがよくあります。