web-dev-qa-db-ja.com

イニシャライザリストが利用できるのに、なぜ今、可変個引数を使用するのですか?

イニシャライザリストに対する可変個引数引数の利点は何であるか疑問に思っていました。どちらも同じ機能を提供します-関数に無数の引数を渡すことができます。

私が個人的に考えるのは、初期化リストはもう少しエレガントです。構文はそれほど厄介ではありません。

また、引数の数が増えるにつれて、イニシャライザリストのパフォーマンスが大幅に向上するようです。

では、Cで可変引数を使用する可能性に加えて、何が欠けているのでしょうか?

31
dtech

可変引数で省略記号を意味する場合(void foo(...)のように)、それらは可変個引数テンプレートによって多かれ少なかれ廃止されます。イニシャライザリストよりも-SFINAEを使用して(たとえば)型の特性を実装するために楕円を使用したり、Cとの互換性を維持したりするためのユースケースがまだあるかもしれませんが、ここでは通常のユースケースについて説明します。

実際、可変個引数テンプレートでは、引数パックにさまざまな型(実際にはany型)を使用できますが、初期化子リストの値は、初期化子の基になる型に変換可能である必要がありますリスト(および絞り込み変換は許可されていません):

#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
}

このため、引数の型が実際に同種であることが意図されていない限り、型の推定が必要な場合、初期化子リストはあまり使用されません。一方、可変長テンプレートは、楕円可変長引数リストのtype-safeバージョンを提供します。

また、初期化子リストを受け取る関数を呼び出すには、引数を中括弧のペアで囲む必要がありますが、可変引数引数パックを受け取る関数の場合はそうではありません。

最後に(まあ、他の違いがありますが、これらはあなたの質問に関連するものです)、初期化子リストの値は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
}
50
Andy Prowl

簡単に言うと、Cスタイルの可変関数は、C++スタイルの可変テンプレートよりもコンパイル時に生成されるコードが少ないため、バイナリサイズや命令キャッシュのプレッシャーが心配な場合は、テンプレートではなく可変引数を使用して機能を実装することを検討してください。

ただし、可変個引数テンプレートは非常に安全で、はるかに使いやすいエラーメッセージを生成するため、オフラインの可変個引数関数をインラインの可変個引数テンプレートでラップし、ユーザーにテンプレートを呼び出させることがよくあります。

1
Jeffrey Yasskin