C++:式の型情報のみを抽出

type2type を用いた、型情報の抽出について、実験&理解したことをまとめておく。type2type は型だけを保持する構造体だ。

template< class T > struct type2type{};

それは
type2type< int >
type2type< float >
type2type< Widget >
などとして、それぞれ int, float, Widgetといった種々の型を「保持」した情報として、具現化され、利用される。
ヘルパー関数テンプレートと併用して、以下のように簡単に作成/利用できる。

#include <iostream>
using namespace std;

// 任意型保持クラス
template < class T > class type2type{ 
	// Hiは、あくまでTの型確認用。結果確認には、
	// 本クラスの空のスーパークラスを作ってもよいが。
public: void Hi(void){} 
};
// ヘルパー関数テンプレート
template < class T > type2type< T > type2type_h( const T& t ){ 
	return type2type< T >();
}
// T型確認用の特殊化
template <> class type2type< int >{ 
public: void Hi( void){ cout<< "imint"<< endl; } };
template <> class type2type< char >{ 
public: void Hi( void){ cout<< "imchar"<< endl; }
};
int main( int argc, char* argv[] )
{
	char a;
	int b;
	type2type_h( a ).Hi();
	type2type_h( b ).Hi();
	return 0;
}

上記main()関数の実行結果は、以下のようになる。
imchar
imint
作成されたtype2typeオブジェクトの中に、正しく型が保持されていることがわかる。
ところで、式(expression)a, b が、変数ではなく、関数だったらどうだろう、つまり、それぞれ、char, int を返す関数の返り値を直接指定するとどうだろう。

char hichar( void ){
	cout<< "hichar"<< endl;
	char a='a';
	return a;
}
int hiint( void ){
	cout<< "hiint"<< endl;
	int a=99;
	return a;
}
// 上記のような関数があったとして、main()関数を以下のようにしてみる
int main( int argc, char* argv[] )
{
	type2type_h( hichar() ).Hi();
	type2type_h( hiint() ).Hi();
	return;
}

これを実行した結果は、以下のようになる。
hichar
imchar
hiint
imint

この場合は、hichar(), hiint()関数を実際に呼び出してしまっているので、当然と言えば当然なのだが、このような場合でも、できれば関数そのものは呼び出してほしくない、その型情報だけが欲しい、という場合がある。
そんなことができるのだろうか?驚いたことに出来てしまうのである。
改めて問題を記述し直して見れば、式(expressioin)の評価をする(evaluate)ことなしに、その型情報のみを吸い出す操作を実現する手法を考えたい、つまり、関数を呼び出すことなく、その関数の返り値の型のみを、取得したい、ということになる。勿論、ここで取り出したいと言っている「型」情報とは、具体的にはtype2typeオブジェクトとして、だ。
それを実現するためには、Cの3項演算子(conditional operator)を使うというのだ。こんなことはコンパイラ開発の専門家でもない限り思いつかないのではないだろうか?
それを実現するのが以下のコードだ。

class any_type2type{
public: 
	template< class T > operator type2type< T >() { 
		return type2type< T >(); 
	};
};
int main( int argc, char* argv[] )
{
	( !0? any_type2type(): type2type_h( hichar() ) ).Hi();
	( !0? any_type2type(): type2type_h( hiint() ) ).Hi();
	return 0;
}

これを実行した結果は、一番最初の、変数を渡した場合と同じく、以下のようになる。
imchar
imint

記述されている、hichar(), hiint() 呼び出しがそれぞれ、評価されない理由は、分岐式が!0(true)となっていることから、一目瞭然であろう。この3項演算子とは、それ自体が式になっているわけで、一体自分がどの型を返したらいいのかを、決めなくてはならない宿命を持っている。

a ? b: c

と書いたときに、b と c の型が違っていた場合、a 式の真偽を評価するずっと以前に、まず「どの型を返したらよいのか?」ということがコンパイル時に決定されるのである。想像に難くないと思うが、b と c の型が違っていた場合、どっちの型を返したらよいのか?あるいは何か別の型を返すようにするか?などということを、決める仕組みをコンパイラは持っているわけだ。そこをどう判断するのか?ということは、ここでは問題ではないので、コンパイラ設計者に任せるとしよう。気になる人は自分がよく使うコンパイラの仕様を確認して欲しい。そうではなくて、ここでは、つまり、b と c については、どちらかの式の評価は動的にされるが、それぞれの型の評価と最終的な出力の型の決定は、コンパイル時に行われるのだ、という事実さえ知っていればそれで十分だ。

この場合は、どんなtype2type型にでもなれる、any_type2typeという特殊なオブジェクトの存在により、第3項の、

type2type_h( hichar/int() )

の部分は、その型のみが評価され、この3項演算子の返り値型として決定される、ということを利用している、というわけである。
any_type2typeについての補足をしておこう。
唯一のoperatorメソッドは関数オブジェクトの話で出てくるあの()演算子ではない。これは「conversion operator」である。(よく見て欲しい、これは()演算子ではない。それは返り値の型すら持たない。)つまり、本any_type2typeは、任意のtype2type型に変換可能であるという保証をそのままコードにしただけの代物である。