最も簡単な解決策は次のとおりです。
uint evaluate(std::function<uint(uint)> func) const {
return func(m_val);
}
ステップアップは、を書くことfunction_view
です。
uint evaluate(function_view<uint(uint)> func) const {
return func(m_val);
}
(ネット上には数十の実装があり、簡単に見つけることができます)。
最も簡単で最も実行効率の高い方法は次のとおりです。
template<class F>
uint evaluate(F&& func) const {
return func(m_val);
}
私たちは何が何であるかを気にしないのでfunc
、アヒルのように鳴きたいだけです。早めにチェックしたい方は…
template<class F> requires (std::is_convertible_v< std::invoke_result_t< F&, uint >, uint >)
uint evaluate(F&& func) const {
return func(m_val);
}
c++20を使用する、またはc++14を使用する
template<class F,
std::enable_if_t<(std::is_convertible_v< std::invoke_result_t< F&, uint >, uint >), bool> = true
>
uint evaluate(F&& func) const {
return func(m_val);
}
これは似ていますが、よりあいまいです。
fix-signature type-erased を書くこともできますがFunctor
、それは悪い考えだと思います。次のようになります。
template<class R, class...Args>
using FixedSignatureFunctor = Functor< std::function<R( std::function<R(Args...)>, Args...) > >;
またはわずかに効率的
template<class R, class...Args>
using FixedSignatureFunctor = Functor< function_view<R( std::function<R(Args...)>, Args...) > >;
しかし、これはかなり正気ではありません。が何であるかを忘れたいと思うでしょうが、 !F
を置き換えることができるわけではありません。F
これを完全に「便利」にするには、スマート コピー/移動/割り当て操作を に追加する必要があります。各操作内の をFunctor
コピーできる場合はF
コピーできます。
template <class F>
class Functor {
public:
// ...
Functor(Functor&&)=default;
Functor& operator=(Functor&&)=default;
Functor(Functor const&)=default;
Functor& operator=(Functor const&)=default;
template<class O> requires (std::is_constructible_v<F, O&&>)
Functor(Functor<O>&& o):m_f(std::move(o.m_f)){}
template<class O> requires (std::is_constructible_v<F, O const&>)
Functor(Functor<O> const& o):m_f(o.m_f){}
template<class O> requires (std::is_assignable_v<F, O&&>)
Functor& operator=(Functor<O>&& o){
m_f = std::move(o.mf);
return *this;
}
template<class O> requires (std::is_assignable_v<F, O const&>)
Functor& operator=(Functor<O> const& o){
m_f = o.mf;
return *this;
}
// ...
};
( c++20バージョン、required 句をc++17std::enable_if_t
以前の SFINAE ハックに置き換えます)。
決定方法
ここで覚えておくべき重要なことは、C++ には複数の種類のポリモーフィズムがあり、間違った種類のポリモーフィズムを使用すると多くの時間を無駄にすることです。
コンパイル時ポリモーフィズムと実行時ポリモーフィズムの両方があります。コンパイル時ポリモーフィズムのみが必要な場合に実行時ポリモーフィズムを使用するのは無駄です。
次に、各カテゴリには、さらに多くのサブタイプがあります。
std::function
ランタイム ポリモーフィック タイプの消去通常オブジェクトです。継承ベースの仮想関数は、もう 1 つのランタイム ポリモーフィック手法です。
あなたの Y コンビネーターは、コンパイル時のポリモーフィズムを行っています。保存するものを変更し、より統一されたインターフェイスを公開しました。
そのインターフェースと通信するものは、Y コンビネーターの内部実装の詳細を気にしません。それらを実装に含めることは、抽象化の失敗です。
evaluate
呼び出し可能なものを受け取り、それを渡し、戻り値uint
を期待しuint
ます。それが気になるところです。渡されるか関数ポインタが渡されるかは気にしません。Functor<Chicken>
それを気にするのは間違いです。
を取る場合は、std::function
ランタイム ポリモーフィズムを行います。template<class F>
type の引数で を受け取る場合F&&
、コンパイル時のポリモーフィックです。これは選択であり、それらは異なります。
あらゆる種類のを取得することFunctor<F>
は、基本的に気にするべきではない API に契約要件を入れることです。