Hatena::ブログ(Diary)

プログラミングの教科書を置いておくところ このページをアンテナに追加 RSSフィード

はじめに

はじめに

新人教育などの授業用に書いたもののうちからいくつかをここに置くことにしました
実際に授業に使ったものもありますし、書いただけで使っていないものもあります。中にはずっと昔に書いたものもあるので幾分古い感じのするものもあるかもしれませんが現在でも有用そうなものを選びました

2010-11-04 C++ SFINAE

C++ SFINAE

SFINAE とは

SFINAE は、Substitution Failure Is Not An Error の略です

これは、テンプレートの展開途中に文法違反になるような候補があったとしても、他の候補があるのであればその文法違反はエラーとせずに無視するという仕様です

と言っても何のことを言っているのかわからないかもしれませんが、大丈夫です

具体的な例を見れば SFINAE というのは実はとても簡単な仕組みに SFINAE という名前を付けているだけなんだということがわかると思います

標準での言及箇所は・・・・・・わかりません

標準の .pdf を substitution で検索すると 14.8 辺りにそれっぽいところはあります

n1905
14 Templates
14.8 Function template specializations
14.8.2 Template argument deduction
2 ...
If a substitution in a template parameter or in the function type of the function template results in an invalid type, type deduction fails.

SFINAE の例

ここに配列とコンテナの先頭と末尾を返してくれる begin, end というテンプレート関数があります

特に何でもないもののように見えると思いますが、実はここには SFINAE の仕組みが働いています

// 配列
template<typename value_type_, int _size> const value_type_* begin( const value_type_ (&a)[_size] ){
	debug_printf( "配列:const begin\n" );
	return	&a[0];
}
template<typename value_type_, int _size> const value_type_* end( const value_type_ (&a)[_size] ){
	debug_printf( "配列:const end\n" );
	return	&a[_size];
}
template<typename value_type_, int _size> value_type_* begin( value_type_ (&a)[_size] ){
	debug_printf( "配列:begin\n" );
	return	&a[0];
}
template<typename value_type_, int _size> value_type_* end( value_type_ (&a)[_size] ){
	debug_printf( "配列:end\n" );
	return	&a[_size];
}
// コンテナ
template<typename T> typename T::const_iterator	begin( const T&	container ){
	debug_printf( "コンテナ:const begin\n" );
	return	container.begin();
}
template<typename T> typename T::const_iterator	end( const T&	container ){
	debug_printf( "コンテナ:const end\n" );
	return	container.end();
}
template<typename T> typename T::iterator	begin( T&	container ){
	debug_printf( "コンテナ:begin\n" );
	return	container.begin();
}
template<typename T> typename T::iterator	end( T&	container ){
	debug_printf( "コンテナ:end\n" );
	return	container.end();
}

int	main(){
	const int	a[10] = {};
	std::vector<int>	b;
	if( begin( b ) == end( b )){
		debug_printf( "begin( b ) == end( b )\n" );
	}
	else{
		debug_printf( "begin( b ) != end( b )\n" );
	}
	if( begin( a ) == end( a )){
		debug_printf( "begin( a ) == end( a )\n" );
	}
	else{
		debug_printf( "begin( a ) != end( a )\n" );
	}
	return	0;
}

この begin, end の実装が配列用だけ、コンテナ用だけであった場合を考えてみましょう

まずコンテナ用の実装だけであった場合には実引数に配列を渡してしまうと、配列には iterator や const_iterator といった型も begin や end といったメンバ関数も無いのでコンパイルエラーになってしまいます

配列とコンテナをどちらも渡したいということだと、これでは困りますね

一方、配列用の実装だけであった場合には実引数に配列を渡してもコンテナを渡してもどちらも [] で要素にアクセスすることができるので何も問題はありません

SFINAE の仕組みがないなら配列とコンテナの両方を渡す目的にはこちらを使うしかありません

配列用の実装とコンテナ用の実装と両方がある場合にもコンテナ用の実装に配列を渡してしまうとコンパイルエラーになってしまうということ自体は何も変わらないのですが、実際には両方の実装がある場合には適切な実装が選択されるので配列がコンテナ用の実装に渡されるというようなことは起きず、コンパイルエラーにはなりません

これはコンパイラが適切な実装を選んでくれる仕組みがあるからです

このコンパイラが適切な実装を選んでくれるという機能は言い換えると、コンパイルエラーになるような実装があっても他に適切な実装がある場合には他の実装で出るコンパイルエラーは無視するということです

これを英語で言ったのが Substitution Failure Is Not An Error (SFINAE) です

SFINAE とテンプレートメタプログラミング

SFINAE は本来は上記の例のような問題を解決するために導入されたルールなのですが、現在はテンプレートメタプログラミングの方面でも様々に利用されています

SFINAE の仕組みを使って例えば次のように、指定した型が特定のメンバ変数を持っているかどうかを確認することもできます

// 指定された型 T は int 型の foo という名前の非 static メンバ変数を持っているか
template<typename T> class has_int_foo {
	struct detail {
		struct sfinae_helper_result_type {};
		template<typename T,int T::*> struct sfinae_helper_t { typedef sfinae_helper_result_type	type; };
		template<typename T,typename U = sfinae_helper_result_type> struct has_int_foo {
			static const bool	value = false;
		};
		template<typename T> struct	has_int_foo<T,typename sfinae_helper_t<T,&T::foo>::type> {
			static const bool	value = true;
		};
	};
public:
	static const bool	value = detail::has_int_foo<T>::value;
};

ここで注目してほしいのは detail::has_int_foo の第二引数が detail::sfinae_helper_t<T,&T::foo>::type となっているところです

sfinae_helper_t の第二引数は int T::* と宣言されているので、型 T が foo という名前の int 型の非 static メンバ変数を持っているのでなければコンパイルエラーになります

コンパイルエラーにならない場合には sfinae_helper_t の type は常に sfinae_helper_result_type ですから detail::has_int_foo の value は true になります

sfinae_helper_t の評価がコンパイルエラーになる場合には detail::has_int_foo の value は false になります

これは次のようなテンプレートを見ると簡単です

struct foo_default_type {};
template<typename T,typename U = foo_default_type> struct foo_t {
	static const int	value = 0;
};
template<typename T> struct foo_t<T,foo_default_type> {
	static const int	value = 1;
};

foo_t<T>::value は 1 でしょうか 0 でしょうか

この場合、U のデフォルト引数が foo_default_type なので foo_t<T> は foo_t<T,foo_default_type> に展開されます

foo_t<T,foo_default_type> は特殊化されている実装があるので、それが採用されます

従って foo_t<T>::value は常に 1 になります

これはどうでしょうか

struct foo_default_type {};
template<typename T,int T::*> struct foo_sfinae_helper_t { typedef foo_default_type	type; };
template<typename T,typename U = foo_default_type> struct foo_t {
	static const int	value = 0;
};
template<typename T> struct foo_t<T,foo_sfinae_helper_t<T,&T::foo>::type> {
	static const int	value = 1;
};

特殊化の foo_default_type だったところが foo_sfinae_helper_t<T,&T::foo>::type になりましたが他は同じです

ですから深く考えなくても foo_t<T>::value は常に 1 です

ただし、正常にコンパイルできる場合は、です

foo_sfinae_helper_t<T,&T::foo>::type がコンパイルできないなら特殊化された実装は採用されません

ここで SFINAE の決まりにより、他の候補があるのでそちらの実装が採用され、特殊化された実装でのエラーは無視されます

そうなると foo_t<T>::value は 0 です

このように SFINAE という仕組みを使うとコンパイルエラーになるかならないかという判定器を作り出すことができるようになりました

今日ではこれがテンプレートメタプログラミングで様々に利用されるようになっています

SFINAE という仕組みはいつの間にか C++ の基盤技術の一つになっているので覚えておくといいと思います

試しに、指定した型が特定の型を持っているかどうかを確認する判定器を書いてみましょう

指定した型が特定の型を持っているか

指定した型が特定の型を持っているかどうかを確認するには、例えば次のようにします

// 指定された型 T は value_type という名前の型を持っているか
template<typename T> class has_value_type {
	struct detail {
		struct true_type { int unused[1]; };
		struct false_type { true_type unused[2]; };
		template<typename U> static true_type sfinae_helper( typename U::value_type* );
		template<typename U> static false_type sfinae_helper(...);
		template<typename T> struct	has_value_type {
			static const bool	value = ( sizeof( sfinae_helper<T>(0)) != sizeof( false_type ));
		};
	};
public:
	static const bool	value = detail::has_value_type<T>::value;
};

指定した型が特定の static なメンバ関数を持っているか

指定のクラスが create_instance という static メソッドを持っていたらそれでインスタンスを作り、持っていなければ new でインスタンスを作るというようなことも、SFINAE の仕組みを利用すると実現できます

template<typename T> class create_instance_t {
	struct detail {
		struct sfinae_helper_result_type {};
		template<typename T,T* (*)()> struct sfinae_helper_t { typedef sfinae_helper_result_type	type; };
		template<typename T,typename U = sfinae_helper_result_type> struct create_instance_t {
			static T*	create_instance(){
				return	new T;
			}
		};
		template<typename T> struct	create_instance_t<T,typename sfinae_helper_t<T,&T::create_instance>::type> {
			static T*	create_instance(){
				return	T::create_instance();
			}
		};
	};
public:
	static T*	create_instance(){
		return	detail::create_instance_t<T>::create_instance();
	}
};
struct CFoo {
	static CFoo*	create_instance(){
		debug_printf( "CFoo::create_instance\n" );
		return	new CFoo;
	}
private:
	CFoo(){
		debug_printf( "CFoo::CFoo\n" );
	}
};
struct CBar {
	CBar(){
		debug_printf( "CBar::CBar\n" );
	}
};

int	main(){
	//CFoo*	foo = new CFoo;	// CFoo::CFoo が private だから直接 new することはできない
	CFoo*	foo = create_instance_t<CFoo>::create_instance();
	CBar*	bar = create_instance_t<CBar>::create_instance();
	delete bar;
	delete foo;
	return	0;
}

指定された型が仕様に合うか

さまざまな条件を組み合わせることもできます

下記の例は派生クラスが基底クラスの非仮想関数の名前を隠していないかどうかをチェックするものです

struct my_concept {
	struct violation {};
	// 派生クラスが fooメンバ関数を持っていたらダメ
	template<typename T,void (T::*)()> struct final_foo_helper_t { typedef violation	type; };
	template<typename T,typename U = violation> struct final_foo {};
	template<typename T> struct	final_foo<T,typename final_foo_helper_t<T,&T::foo>::type>;
	// 派生クラスが barメンバ関数を持っていたらダメ
	template<typename T,void (T::*)()> struct final_bar_helper_t { typedef violation	type; };
	template<typename T,typename U = violation> struct final_bar {};
	template<typename T> struct	final_bar<T,typename final_bar_helper_t<T,&T::bar>::type>;
};

class cfoo;
template<> class my_concept::final_foo<cfoo> {};
template<> class my_concept::final_bar<cfoo> {};

template<typename T> class my_resquires : my_concept::final_foo<T>, my_concept::final_bar<T> {};
template<typename T> class my_checker : my_resquires<T> {};

class cfoo {
public:
	void	foo(){
		debug_printf( "foo\n" );
	}
	void	bar(){
		debug_printf( "bar\n" );
	}
	cfoo(){
	}
};
class cbar : public cfoo {
public:
	cbar():
	cfoo()
	{
	}
};
class cbaz : public cfoo {
public:
	void	foo(){
		debug_printf( "baz\n" );
	}
	cbaz():
	cfoo()
	{
	}
};
class cqux : public cfoo {
public:
	void	bar(){
		debug_printf( "qux\n" );
	}
	cqux():
	cfoo()
	{
	}
};

static_assert( sizeof( my_checker<cfoo> ), "cfoo: concept violation" );
static_assert( sizeof( my_checker<cbar> ), "cbar: concept violation" );
static_assert( sizeof( my_checker<cbaz> ), "cbaz: concept violation" ); // foo を持っているからダメ
static_assert( sizeof( my_checker<cqux> ), "cqux: concept violation" ); // bar を持っているからダメ

int	main(){
	cfoo	foo;
	cbar	bar;
	cbaz	baz;
	cqux	qux;
	foo.foo();
	bar.foo();
	baz.foo();
	qux.bar();
	return	0;
}

この実装は可変長テンプレート引数(variadic template)を使えるなら次のようにすることもできます

template<typename... concepts> class my_resquires : concepts... {};
template<typename T> class my_checker : my_resquires<my_concept::final_foo<T>, my_concept::final_bar<T>> {};

static_assert( sizeof( my_checker<cfoo> ), "cfoo: concept violation" );
static_assert( sizeof( my_checker<cbar> ), "cbar: concept violation" );
static_assert( sizeof( my_checker<cbaz> ), "cbaz: concept violation" ); // foo を持っているからダメ
static_assert( sizeof( my_checker<cqux> ), "cqux: concept violation" ); // bar を持っているからダメ

また static_assert を有効に使えるように不完全型を利用したエラーの代わりに評価結果を bool値で返すようにすることもできます

struct my_concept {
	struct violation {};
	struct false_type { static const bool	value = false; };
	struct true_type { static const bool	value = true; };
	// foo
	template<typename T,void (T::*)()> struct final_foo_helper_t { typedef violation	type; };
	template<typename T,typename U = violation> struct final_foo : public true_type {};
	template<typename T> struct	final_foo<T,typename final_foo_helper_t<T,&T::foo>::type> : public false_type {};
	// bar
	template<typename T,void (T::*)()> struct final_bar_helper_t { typedef violation	type; };
	template<typename T,typename U = violation> struct final_bar : public true_type {};
	template<typename T> struct	final_bar<T,typename final_bar_helper_t<T,&T::bar>::type> : public false_type {};
};
template<typename... concepts> struct my_requires {
	template<int, typename concept, typename... concepts_> struct my_requires_next {
		static const bool	value = concept::value && my_requires_next<sizeof...(concepts_),concepts_...>::value;
	};
	template<typename concept> struct my_requires_next<1,concept> {
		static const bool	value = concept::value;
	};
	static const bool	value = my_requires_next<sizeof...(concepts),concepts...>::value;
};

class cfoo;
template<> class my_concept::final_foo<cfoo> : public my_concept::true_type {};
template<> class my_concept::final_bar<cfoo> : public my_concept::true_type {};

template<typename T> struct my_checker : public my_requires<my_concept::final_foo<T>,my_concept::final_bar<T>> {};

static_assert( my_checker<cfoo>::value, "cfoo: concept violation" );
static_assert( my_checker<cbar>::value, "cbar: concept violation" );
static_assert( my_checker<cbaz>::value, "cbaz: concept violation" ); // foo を持っているからダメ
static_assert( my_checker<cqux>::value, "cqux: concept violation" ); // bar を持っているからダメ

余談

この SFINAE のようなプログラミングの新しい言葉は覚えるだけでも大変だと思います

この SFINAE のようなアクロニムは元の言葉を知らない人には何も伝わらないという大きな欠点があります

漢字一文字は英単語に相当するようなものなので略語にしても意味が残りますが、アルファベットはそうではありませんからね

プログラミングの世界では、プログラミング、プログラマ、コンピューター、ソフトウェア、アルゴリズムなどなど多くの外国語が訳されないまま使われているので皆さんも覚えるのが大変でしょう

漢字も外国語といえば外国語なので別に外国語のまま理解できるようになってしまってもいいのかもしれませんが、元々外国語であった情報や論理などといった言葉を今外国語で考えなくていいのは訳語があるお蔭です

皆さんも自分なりの訳語を考えてみてはどうでしょう

いつかそれが世に広まって後世のプログラマの苦労が少しは減るかもしれませんよ

広告について

広告は自動的に入るもので当方では一切関知致しません
「ダウンロード」「続きはこちら」などと出ている場合がありますが、それは広告です