サムが気付いたように、MFP
はテンプレートですが、2番目のテンプレート引数std::map
には型が必要です。したがって、マップに関数ポインタを設定する前に、実際の型を取得する必要があります。このケーステンプレートクラスの最も簡単なアプローチを提案しましょう。これを使用すると、オブジェクトのインスタンス化に必要なすべての戻り値の型をリストする必要がありますが、その型のいずれでもCall
にできるようになります。ポインタの代わりにstd::function
を使用しますが、簡単に関数ポインタにロールバックできます。
最初に、クラスのユーザーが必要とするタイプの数がわからないので、それをバリデーションしてみましょう。 map
には完全なタイプが必要なので、タイプごとに1つのマップが必要です。それを得る最も一般的な方法は、私たちのケースではパックの拡張が必要なタプルです。タプルを使用すると、コンパイル時に必要なマップを検索し、実行時にそのマップ内の関数を名前で検索できます。説明してコードを見てみましょう:
template<typename ...Types>
class B {
private:
// Template alias for std::function.
template<typename T>
using MFP = std::function<T()>;
/* Tuple of maps from std::string to MFP for all types
in Types parameter pack. */
std::tuple<std::map<std::string, MFP<Types>>...> fmap;
template<typename T>
T f() { return 2.5; }
template<typename T>
T g() { return 1.0f; }
// Call implementation with compile-time pattern matching.
// T is return type, U is current matching type
template<typename T, size_t idx, typename U, typename ...Ts>
struct CallImpl {
static T callImpl(B* this_ptr, const std::string & s) {
/* If we exhausted Ts pack, we have no proper instance for
requested return type. Let's print a human-readable
compilation error message. */
static_assert((sizeof ... (Ts)) > 0,
"Requested return type not found.");
/* Otherwise discard U, increment tuple index
and try the next type. */
return CallImpl<T, idx + 1, Ts...>::callImpl(this_ptr, s);
}
};
/* This partial specialization is called when return
* type (T in above declaration) matches
* stored type (U in above declaration). */
template<typename T, size_t idx, typename ...Ts>
struct CallImpl<T, idx, T, Ts...> {
static T callImpl(B* this_ptr, const std::string & s) {
/* First, get the map from tuple by index.
This operation is either always valid in runtime or does not compile.
Next, get function object from map. It may fail in runtime
if user passed invalid string, so consider using map::at
or add any other sensible logic for this case. */
return std::get<idx>(this_ptr->fmap)[s]();
}
};
public:
B() {
/* Populate map with objects. Ellipsis in the last line
expands Types as needed. */
fmap = std::make_tuple(std::map<std::string, MFP<Types>>{
{"f", std::bind(std::mem_fn(&B::f<Types>), this)},
{"g", std::bind(std::mem_fn(&B::g<Types>), this)}
}...);
}
template<typename T>
T Call(const std::string & s) {
/* Start pattern matching with zero index. */
return CallImpl<T, 0, Types...>::callImpl(this, s);
}
};
使用法:
int main() {
B<int, float, short> a; // Provides int, float and short return types.
std::cout << a.Call<int>("f") << std::endl; // Prints 2, which is 2.5 casted to int.
std::cout << a.Call<float>("f") << std::endl; // Prints 2.5
// Compilation error with "Requested type not found." message among others.
std::cout << a.Call<double>("f") << std::endl;
}
いくつかの注意:f
宣言で
2.5
はリテラル、二重ですが、ダブルではありませんB<int, float> a;
に記載されていますが、これはでコンパイルエラーが発生します。
- コード
Call
とcallImpl
short
の機能はインスタンス化されていないため、まったく生成されません。
'std :: map> fmap;'のようなテンプレート引数を指定する必要があります。それ以外の場合、 'A :: fmap'は' A a; 'と書くだけでインスタンス化できます。 –
songyuanyao