#動機
例えば有るデバイスについて情報を全て握ってるオブジェクトがあったとして、「メモリの量は?」てきいたらunsigned intで返ってくるけど、「IPアドレスは?」てきいたらstringで返ってくるみたいな事がテンプレートでできないかなと思ってたら、OpenCLで遊んでいると、テンプレートにENUM入れて戻り値の型を変えている以下のような関数があって、便利だなと思った。
template <cl_int name> typename detail::param_traits<detail::cl_device_info, name>::param_type cl::Device::getInfo(void)
便利なだけに、難解なテンプレ。テンプレート初心者としては辛い。これっぽいのを作るのがこの記事の目的。
#template <cl_int name> の意味
まず、
template <cl_int name> ...
という部分
template <typename T>
というおなじみの実装からは程遠い。
そもそもこれが何をやってるかが意味わからんと思ってたら、こういうことらしい。すげえ簡単だった。
#include <iostream>
using namespace std;
template <int NUMBER>
void printParameter(){
cout << NUMBER << endl;
};
int main(){
printParameter<30>();
return 0;
}
てな感じでコンパイル時に、関数内のnumber を30で置き換えてくれる。
だから、これを応用すれば以下のようにテンプレート引数をベースに関数内でswitch分岐が出来る。
#include <iostream>
using namespace std;
enum class Query{
MEMORY_SIZE,
IP_ADDRESS
};
template <Query query>
void printParameter(){
switch(query) {
case Query::MEMORY_SIZE:
cout << "memory size was asked" << endl;
break;
case Query::IP_ADDRESS:
cout << "IP address was asked" << endl;
break;
}
};
int main(){
printParameter<Query::MEMORY_SIZE>();
printParameter<Query::IP_ADDRESS>();
return 0;
}
これで結果は、
memory size was asked
IP address was asked
しっかり分岐できている。
ここまではOKだが、戻り値をこのパラメータによって変化させるのがまだ実装されていない。
typenameがテンプレートの中に出てくる時の意味
読解目標のOpenCLの関数の宣言の
typename detail::param_traits<detail::cl_device_info, name>::param_type
がおそらく戻り値の宣言で、動的に戻り値が変わるトリックがここにあると思われる。
param_traitsはcl.hpp中で
template <typename enum_type, cl_int Name>
struct param_traits {};
と宣言されている空っぽのクラス、traitsは「特徴」という意味。このダミーなテンプレート宣言に対して、明示的特殊化を加えまくる方法で、返り値の型の変化を実装してるっぽい。その後にこんなのが続く
#define __CL_DECLARE_PARAM_TRAITS(token, param_name, T) \
struct token; \
template<> \
struct param_traits<detail:: token, param_name> \
{ \
enum { value = param_name }; \
typedef T param_type; \
};
(中略)
#ifdef CL_DEVICE_PROFILING_TIMER_OFFSET_AMD
__CL_DECLARE_PARAM_TRAITS(cl_device_info, CL_DEVICE_PROFILING_TIMER_OFFSET_AMD, cl_ulong)
#endif
このマクロはもし、CL_DEVICE_PROFILING_TIMER_OFFSET_AMDが宣言されていた場合
struct param_traits{
enum { value = CL_DEVICE_PROFILING_TIMER_OFFSET_AMD };
typedef cl_ulong param_type;
};
と展開される。なるほど。。。こんなことよく考える。
簡略化したコードで書いて見ると以下のようになる。
template <Query query>
struct Test_Traits{}; // empty class
template<>
struct Test_Traits<Query::MEMORY_SIZE>{
typedef unsigned int type;
};
template<>
struct Test_Traits<Query::IP_ADDRESS>{
typedef string type;
};
それでこれら2つを組み合わせると、戻り値がパラメータによって変える事が可能となる。
#include <iostream>
using namespace std;
enum class Query{
MEMORY_SIZE,
IP_ADDRESS
};
template <Query query>
struct Test_Traits{}; // empty class
template<>
struct Test_Traits<Query::MEMORY_SIZE>{
typedef unsigned int type;
};
template<>
struct Test_Traits<Query::IP_ADDRESS>{
typedef string type;
};
template <Query query>
typename Test_Traits<query>::type printParameter(){
typename Test_Traits<query>::type returnValue;
switch(query) {
case Query::MEMORY_SIZE:
cout << "return value is unsgined int" << ends;
// returnValue = 100;
return returnValue;
break;
case Query::IP_ADDRESS:
cout << "return value is string" << ends;
// returnValue = "192.168.2.3";
return returnValue;
break;
}
};
int main(){
cout << printParameter<Query::MEMORY_SIZE>() << endl;
cout << printParameter<Query::IP_ADDRESS>() << endl;
return 0;
}
ただ、このコードはコンパイルは通るが、printParameter関数の中でreturnValue に何かを代入しようとするとエラーになる。多分テンプレートで型が確定してないのに、リテラル値を入れるなってことだと思う。歯がゆい。
なので、struct側にstatic関数で値を返す方法で対処。
#include <iostream>
using namespace std;
enum class Query{
MEMORY_SIZE,
IP_ADDRESS
};
template <Query query>
struct Test_Traits{}; // empty class
template<>
struct Test_Traits<Query::MEMORY_SIZE>{
typedef unsigned int type;
const static unsigned int getValue(){
return 64;
}};
template<>
struct Test_Traits<Query::IP_ADDRESS>{
typedef string type;
const static string getValue(){
return string("192.168.0.1");
}
};
template <Query query>
typename Test_Traits<query>::type printParameter(){
return Test_Traits<query>::getValue();
};
int main(){
unsigned int memorySize = printParameter<Query::MEMORY_SIZE>();
string ip_address = printParameter<Query::IP_ADDRESS>();
cout << "the memory size is " << memorySize << " and ip address is " << ip_address << endl;
return 0;
}
これで一応、
1.テンプレートパラメータにENUMを渡す
2.それを元に戻り値の形を変える
これができる関数はできた。
OpenCLのヘッダのようにライブラリAPIとしては可読性が高いものになりそうだが...危険性も高そうな..