適当なクラスにある特定のメンバーがあるかないかで処理を切り分ける
に習う感じでやるのがよいか。SFINAE〜って奴です。
ここではToStringというintを引数にとってstd::stringを返すメンバーが任意のクラスにあるかないかで処理を切り分けるような書き方を書いた。
#include <iostream> #include <string> //ToStringクラスがあるかないかを判定する構造体(クラス) template<typename T_> struct HasToStringMethod { template<typename U_, std::string(U_::*)(int) const> struct SFINAE {}; template<typename U_> static char Test(SFINAE<U_, &U_::ToString> *); template<typename U_> static int Test(...); static const bool value = sizeof(Test<T_>(0)) == sizeof(char); }; //お試しの呼び出し template<class T_> void call(T_ t, std::true_type){ std::cout << "There is ToString method." << std::endl; } template<class T_> void call(T_ t, std::false_type){ std::cout << "There is no ToString method." << std::endl; } template<class T_> void call(T_ t){ call(t, std::integral_constant<bool, HasToStringMethod<T_>::value>()); } //ToStringメソッドのある/ないHogeクラス class HogeWithToString { public: std::string ToString(int x)const{ return std::to_string(x); } }; class Hoge{}; int main() { call(Hoge()); call(HogeWithToString()); return 0; }
実行結果
There is no ToString method. There is ToString method.
SFINAEってもんがあんまよくわかってない時に書いたもの
数値→文字列の変換はto_string関数で一発だ in C++11
関数ポインタ、あるいはそれをテンプレートの特殊化で捌く時用のメモ
鳥頭化がひどいのでメモっておこうって。
結論として
- function_traitsの特殊化で、「R_(Arg_)」と「R_(*)(Arg_)」は区別され、それはdecltype(hoge)なのかdecltype(&hoge)なのかに依る
- typeid関数に対しても&を付けてhoge関数を渡すか渡さないかで、void (__cdecl*)(int)となるかvoid (__cdecl)(int)となるかが別れる(上の話と整合的)
- 関数ポインタに関数を渡す時は6つけてもつけなくてもいいし、その関数ポインタから関数を実行する時も、*を付けてもつけなくてもいい
ってところかな。
#include<iostream> //オレオレfunction traits template<typename T_> struct function_traits; template<typename R_, typename Arg_> struct function_traits<R_(Arg_)> { typedef Arg_ argument; static const int X = 1; }; template<typename R_, typename Arg_> struct function_traits<R_(*)(Arg_)> { typedef Arg_ argument; static const int X = 333; }; //適当な関数 void hoge(int x){ std::cout << "I'm hoge" << std::endl; } //Main int main() { std::cout << "hogeに対するtypeidの適用結果" << std::endl; std::cout << "hogeのtypeid" << std::endl; std::cout << typeid(hoge).name() << std::endl; std::cout << "&hogeのtypeid" << std::endl; std::cout << typeid(&hoge).name() << std::endl; std::cout << "*hogeのtypeid" << std::endl; std::cout << typeid(*hoge).name() << std::endl; std::cout << std::endl; std::cout << "function_traitsのR_(Arg_)=1に特殊化されるか、それともR_(*)(Arg_)=333となるか" << std::endl; std::cout << "hogeの場合" << std::endl; std::cout << function_traits<decltype(hoge)>::X << std::endl; std::cout << "&hogeの場合" << std::endl; std::cout << function_traits<decltype(&hoge)>::X << std::endl; std::cout << "*hogeの場合" << std::endl; std::cout << function_traits<decltype(*hoge)>::X << std::endl; std::cout << std::endl; std::cout << "関数ポインタの復習的なもん" << std::endl; //適当にtypdef typedef void(*Func)(int); //typedef void(Gunc)(int); は当然だめ //&つけてもつけなくてもOKぽい Func f = hoge; Func g = &hoge; //*つけてもつけなくてもOKぽい f(1); (*g)(1); return 0; }
実行結果
hogeに対するtypeidの適用結果 hogeのtypeid void __cdecl(int) &hogeのtypeid void (__cdecl*)(int) *hogeのtypeid void __cdecl(int) function_traitsのR_(Arg_)=1に特殊化されるか、それともR_(*)(Arg_)=333となるか hogeの場合 1 &hogeの場合 333 *hogeの場合 1 関数ポインタの復習的なもん I'm hoge I'm hoge
引数の型だけ違う(関数オブジェクト|関数)の処理をtemplateで切り分ける
の続編。関数オブジェクトだけじゃなくて、関数でもOKにしてみた。ラムダ式でも動いた。関数ポインタが混ざってくると混乱する。
#include<iostream> #include<vector> #include<type_traits> //適当なfunction traits template<typename T_> struct function_traits{}; template<typename C_, typename R_, typename Arg_> struct function_traits<R_(C_::*)(Arg_) const>{ typedef Arg_ argument; }; template<typename R_, typename Arg_> struct function_traits<R_(Arg_)>{ typedef Arg_ argument; }; //Tag(Vector or Double) struct tagV{}; struct tagD{}; //Tag(Class(Functor) or Function) struct tagC{}; struct tagF{}; template<typename T_> struct call_traits { typedef tagC object_type; typedef typename std::conditional<std::is_same<typename function_traits<decltype(&T_::operator())>::argument, double>::value, tagD, tagV>::type argument_type; }; template<class R_, class Arg_> struct call_traits<R_(Arg_)> { typedef tagF object_type; typedef typename std::conditional<std::is_same<Arg_, double>::value, tagD, tagV>::type argument_type; }; //タグに応じた実際の処理 template<typename T_> void call_detail(const T_ & t, std::vector<double> x, tagV){ t(x); } template<typename T_> void call_detail(const T_ & t, std::vector<double> x, tagD){ t(x[0]); } template<class T_> void call(const T_ & t, std::vector<double> x) { call_detail(t, x, call_traits<T_>::argument_type()); } //引数の型が違うクラス&関数 void a(double){ std::cout << "I'm function A" << std::endl; } void b(double){ std::cout << "I'm function B" << std::endl; } struct A{ void operator()(double x) const{ std::cout << "I'm class A:" << x << std::endl; } }; struct B{ void operator()(std::vector<double> x)const{ std::cout << "I'm class B:" << x[0] << ", " << x[1] << ", " << x[2] << std::endl; } }; //メイン int main() { std::vector<double> xxx = { 1, 2, 3 }; call(A(), xxx); call(B(), xxx); call(a, xxx); call(b, xxx); call([](double x){std::cout << "I'm lambda." << std::endl; }, xxx); return 0; }
実行結果
I'm class A:1 I'm class B:1, 2, 3 I'm function A I'm function B I'm lambda.
関数ポインタと関数オブジェクト(ファンクタ)のどちらでも保有できるクラス
すなおにこんなんでイケた。すぐ忘れるわぁ。
Hogeにhoge関数でもMogeクラスのオブジェクトでもどっちでも入ると。
#include <iostream> template<class T_> struct Hoge { Hoge(const T_ & x) : x_(x){} double operator()(double x){return x_(x); } T_ x_; }; double hoge(double x){ return x + 10; } struct Moge { double operator()(double x){ return x + 3; } }; int main() { Hoge<double(*)(double)> x1{ hoge }; Hoge<Moge> x2 { Moge()}; std::cout << x1(10) << std::endl; std::cout << x2(10) << std::endl; return 0; }
実行結果
20 13
引数の型だけ違う関数オブジェクトの処理をtemplateで切り分ける−2
記事のまとめ
続いたのでまとめておく。
本文
をもうちょっとtraits使ってるぽく書いた例。結果は同じなので省略。std::conditionalとfunction traitsつかえばもうちょい綺麗になりそう。
#include <iostream> #include <vector> //1引数をとるメンバ関数用のテンプレート&Arg_の型で処理を切り分け namespace moge_traits { //vector/doubleを切り分けるタグ struct tagV{}; struct tagD{}; //適当なクラスメソッド型 template <typename T_, typename Arg_> struct ClassMethod{ typedef void (T_::*type)(Arg_); }; //moge_traitsで処理切り分け用tagのtypedef template<typename T_, typename ClassMethod_> struct traits { typedef tagV tag; }; template<typename T_> struct traits<T_, typename ClassMethod<T_, double>::type>{ typedef tagD tag; }; //API template<typename T_> void operatorInner(T_ t, std::vector<double> x){ detail::operatorInner(t, x, traits<T_, decltype(&T_::operator())>::tag());} namespace detail { //タグに応じた実際の処理 template<typename T_> void operatorInner(T_ t, std::vector<double> x, tagV){ t(x); } template<typename T_> void operatorInner(T_ t, std::vector<double> x, tagD){ t(x[0]); } } } //引数の型が違うクラス struct A{ void operator()(double x){ std::cout << "I'm class A:" << x << std::endl; } }; struct B{ void operator()(std::vector<double> x){ std::cout << "I'm class B:" << x[0] << ", " << x[1] << ", " << x[2] << std::endl; } }; //中でA・Bクラスを使う適当なクラス template<typename T_> struct Moge { Moge(T_ x) : x_(x){} void operator()(std::vector<double> x){ moge_traits::operatorInner(x_, x); } T_ x_; }; //メイン int main() { std::vector<double> xxx = { 1, 2, 3 }; B b; Moge<B> x1(b); x1(xxx); A a; Moge<A> x2(a); x2(xxx); return 0; }
引数の型だけ違う関数オブジェクトの処理をtemplateで切り分ける−1
記事のまとめ
続いたのでまとめておく。
本文
クラスの型自身での条件分岐はよく書いてるけれども引数の型ってのでどうやるんだろうというのをうだうだ書いてたメモ。
メンバ関数の引数をTagに使ってるTag dispatch感ある。
#include <iostream> #include <vector> //1引数をとるメンバ関数用のテンプレート&Arg_の型で処理を切り分け template <typename T_, typename Arg_> struct ClassMethod{typedef void (T_::*Function)(Arg_);}; template<typename T_> void operatorInner(T_ t, std::vector<double> x, typename ClassMethod<T_, std::vector<double>>::Function){t(x);} template<typename T_> void operatorInner(T_ t, std::vector<double> x, typename ClassMethod<T_, double>::Function){ t(x[0]);} //引数の型が違うクラス struct A{ void operator()(double x){ std::cout << "I'm class A:" << x << std::endl; } }; struct B{ void operator()(std::vector<double> x){ std::cout << "I'm class B:" << x[0] << ", " << x[1] << ", " << x[2] << std::endl; } }; //中でA・Bクラスを使う適当なクラス template<typename T_> struct Moge { Moge(T_ x) : x_(x){} void operator()(std::vector<double> x){operatorInner(x_, x, &T_::operator());} T_ x_; }; //メイン int main() { std::vector<double> xxx = { 1, 2, 3 }; B b; Moge<B> x1(b); x1(xxx); A a; Moge<A> x2(a); x2(xxx); return 0; }
実行結果
I'm class B:1, 2, 3 I'm class A:1