テンプレートパラメータにENUMを渡して、それを元に戻り値の型を変えるテンプレート関数を作る


#動機

例えば有るデバイスについて情報を全て握ってるオブジェクトがあったとして、「メモリの量は？」てきいたら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>
```

というおなじみの実装からは程遠い。
そもそもこれが何をやってるかが意味わからんと思ってたら、[こういうこと](http://stackoverflow.com/questions/499106/what-does-template-unsigned-int-n-mean)らしい。すげえ簡単だった。

```
#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;
};

```

それでこれら２つを組み合わせると、戻り値がパラメータによって変える事が可能となる。


```
#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としては可読性が高いものになりそうだが...危険性も高そうな..