適当なクラスにある特定のメンバーがあるかないかで処理を切り分ける

に習う感じでやるのがよいか。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

C++での「数値→文字列」の変換。
昔はstringstreamを使って数行コードを書いて変換しなきゃならんかったもんが、C++11だとto_string関数一発でいけるようになっていた。進化するC++ありがたい。

#include <iostream>
#include <string>

int main() 
{
	const std::string x = std::to_string(123);
	std::cout << x.c_str();
	return 0;
}

関数ポインタ、あるいはそれをテンプレートの特殊化で捌く時用のメモ

鳥頭化がひどいのでメモっておこうって。
結論として

  • 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.

関数ポインタと関数オブジェクト(ファンクタ)のどちらでも保有できるクラス

すなおにこんなんでイケた。すぐ忘れるわぁ。

Hogehoge関数でも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