web-dev-qa-db-ja.com

std :: invokeとstd :: applyの違いは何ですか?

これらはどちらも、関数、メンバー関数、および一般に呼び出し可能なものを呼び出す一般的な方法として使用されます。 cppreferenceから私が見る唯一の本当の違いはstd::invoke関数のパラメーター(パラメーターの数はいくつでも)は関数にforwardedされますが、std::applyパラメータはTupleとして渡されます。これは本当に唯一の違いですか? Tuplesを処理するためだけに別の関数を作成するのはなぜですか?

11
Nikos

これは本当に唯一の違いですか?タプルを処理するためだけに別の関数を作成するのはなぜですか?

あなたが本当に両方のオプションが必要だからです。検討してください:

int f(int, int);
int g(Tuple<int, int>);

Tuple<int, int> tup(1, 2);

invoke(f, 1, 2); // calls f(1, 2)
invoke(g, tup);  // calls g(tup)
apply(f, tup);   // also calls f(1, 2)

notを実行するinvoke(g, tup)Tupleを展開するapply(f, tup)の違いを特に考慮してください。あなたは時々両方を必要とする、それはどういうわけか表現される必要がある。


一般的にこれらは非常に密接に関連する操作であるとあなたは正しい。実際、Matt Calabreseは Argot という名前のライブラリを作成しています。これは、両方の操作を組み合わせて、呼び出す関数ではなく、引数を装飾する方法によって区別します。

call(f, 1, 2);         // f(1,2)
call(g, tup);          // g(tup)
call(f, unpack(tup));  // f(1, 2), similar to python's f(*tup)
13
Barry

次の理由により、std::applyを使用します。

1:std::invokeにアクセスできる場合でも、applyを実装するのは大変なことです。タプルをパラメーターパックに変換するのは簡単な操作ではありません。 applyの実装は次のようになります(cpprefから):

namespace detail {
template <class F, class Tuple, std::size_t... I>
constexpr decltype(auto) apply_impl(F&& f, Tuple&& t, std::index_sequence<I...>)
{
    return std::invoke(std::forward<F>(f), std::get<I>(std::forward<Tuple>(t))...);
}
}  // namespace detail

template <class F, class Tuple>
constexpr decltype(auto) apply(F&& f, Tuple&& t)
{
    return detail::apply_impl(
        std::forward<F>(f), std::forward<Tuple>(t),
        std::make_index_sequence<std::Tuple_size_v<std::remove_reference_t<Tuple>>>{});
}

もちろん、世界で最もコードを書くのは難しいことではありませんが、簡単なことではありません。特に、index_sequenceメタプログラミングトリックを行わない場合は。

2:Tupleの要素をアンパックして関数を呼び出すのはかなり便利です。それがサポートするために存在する基本的な操作は、引数のセットをパッケージ化し、そのセットを渡し、それらのパラメーターを使用して関数を呼び出す機能です。技術的には、単一のパラメーターで(値を渡すことで)実行できますが、applyを使用すると、複数のパラメーターで実行できます。

また、言語間のマーシャリングをメタプログラムで行うなど、メタプログラミングのトリックを行うこともできます。このようなシステムに関数を登録すると、関数のシグネチャ(および関数自体)が与えられます。その署名は、メタプログラミングを通じてデータをマーシャリングするために使用されます。

他の言語が関数を呼び出すと、メタプログラムによって生成された関数がパラメーター型のリストを調べ、それらの型に基づいて他の言語から値を抽出します。それらは何に抽出されますか?値を保持するある種のデータ構造。また、メタプログラミングではstruct/classを(簡単に)構築できないため、代わりにTupleを構築します(実際、このようなメタプログラミングのサポートは、Tupleが存在する理由の80%です)。

Tuple<Params>が作成されたら、std::applyを使用して関数を呼び出します。 invokeでそれを実際に行うことはできません。

3:Tupleと同等の機能を実行できるようにするためだけに、パラメータをinvokeに固定したくない場合。

4:invokeingをとる関数のTupleingと、applyingを解凍するためのTupleingの違いを確立する必要があります。結局のところ、ユーザーが指定したパラメーターに対してinvokeを実行するテンプレート関数を作成している場合、ユーザーがたまたま単一のTupleを引数として指定した場合、それはひどいことになります。 invoke関数で解凍しました。

他の方法を使用してケースを区別することもできますが、単純なケースでは異なる機能を使用することで十分です。より一般化されたapplyスタイルの関数を作成していて、他の引数を渡すことに加えてTuplesをアンパックしたり、引数リストに複数のタプルをアンパックしたりしたい場合(またはこれら)、それを処理できる特別なsuper_invokeが必要です。

しかし、invokeは単純なニーズのための単純な関数です。 applyについても同様です。

5
Nicol Bolas