これらはどちらも、関数、メンバー関数、および一般に呼び出し可能なものを呼び出す一般的な方法として使用されます。 cppreferenceから私が見る唯一の本当の違いはstd::invoke
関数のパラメーター(パラメーターの数はいくつでも)は関数にforward
edされますが、std::apply
パラメータはTuple
として渡されます。これは本当に唯一の違いですか? Tuple
sを処理するためだけに別の関数を作成するのはなぜですか?
これは本当に唯一の違いですか?タプルを処理するためだけに別の関数を作成するのはなぜですか?
あなたが本当に両方のオプションが必要だからです。検討してください:
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)
次の理由により、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:invoke
ingをとる関数のTuple
ingと、apply
ingを解凍するためのTuple
ingの違いを確立する必要があります。結局のところ、ユーザーが指定したパラメーターに対してinvoke
を実行するテンプレート関数を作成している場合、ユーザーがたまたま単一のTuple
を引数として指定した場合、それはひどいことになります。 invoke
関数で解凍しました。
他の方法を使用してケースを区別することもできますが、単純なケースでは異なる機能を使用することで十分です。より一般化されたapply
スタイルの関数を作成していて、他の引数を渡すことに加えてTuple
sをアンパックしたり、引数リストに複数のタプルをアンパックしたりしたい場合(またはこれら)、それを処理できる特別なsuper_invoke
が必要です。
しかし、invoke
は単純なニーズのための単純な関数です。 apply
についても同様です。