読者です 読者をやめる 読者になる 読者になる

とくにあぶなくないRiSKのブログ

危ないRiSKのブログだったかもしれない。本当はRiSKだけどググラビリティとか取得できるIDの都合でsscriskも使ったり。

テンプレートの値で関数の返却値の型を変える

phobos のソースを読んでて気付いたのですが,D言語ではテンプレートの値によって関数テンプレートの返却値の型を変えることができます。(そう見えますという話。)

import std.stdio;

int f(int n)() if(n)
{
 return n;
}

string f(int n)() if(!n)
{
 return "zero";
}

void main()
{
 writeln(f!1()); // int を返す
 writeln(f!0()); // string を返す
}

実行結果:

1
zero

これと同じ事をC++で関数テンプレートの特殊化を使ってやろうとしても,すんなりとはいきません。

#include<iostream>

template<int n>
int f()
{
 return n;
}

template<>
char const * f<0>()
{
 return "zero";
}

int main()
{
 std::cout << f<1>() << std::endl; // int を返す
 std::cout << f<0>() << std::endl; // char const * を返…さない。コンパイルエラー
}

なぜこのような差が出るのでしょうか?
C++のコードについてコメントをいただきました。

hito_hpp 2011/01/04 17:41
C++では、戻り値の型が違うと別の関数テンプレートだと判断されるので、
この場合、特殊化されるPrimary templateが存在しないんですね。

一方,D言語において f!1() は f!(1).f() の省略形とみなされます。f!(1) というテンプレートの名前空間が作られその中の f の呼出しになります。同様に f!0() は f!(0).f() の省略形とみなされ, f!(0) というテンプレートの名前空間が作られその中の f の呼出しになります。
C++的なイメージでは

#include<iostream>

namespace f_1{
 int f()
 {
  return 1;
 }
}

namespace f_0{
 char const * f()
 {
  return "zero";
 }
}

int main()
{
 std::cout << f_1::f() << std::endl; // int を返す
 std::cout << f_0::f() << std::endl; // char const * を返す
}

実行結果:

1
zero

みたいな事が起きてるみたいです。
C++では namespace にテンプレートを持たせることができないので,もし同じ事をやりたいのだったら クラステンプレートを使うしかないかも。

#include<iostream>

template<int n>
struct s
{
 static int f() { return n; }
};

template<>
struct s<0>
{
 static char const * f() { return "zero"; }
};

int main()
{
 std::cout << s<1>::f() << std::endl; // int を返す
 std::cout << s<0>::f() << std::endl; // char const * を返す
}

実行結果:

1
zero

非常にダサいですね。この問題を美しく解決できる方法があったら誰か教えてください。

おまけ

D言語だとこんなことができるよサンプル。
Ideone.com | Online D Compiler & Debugging Tool

追記:

C++ では enable_if を使うのがスマートみたいです。

Ideone.com | Online C++0x Compiler & Debugging Tool

#include <iostream>
#include <type_traits>
 
template <int N>
typename std::enable_if<N, int>::type f() { return N; }
template <int N>
typename std::enable_if<!N, std::string>::type f() { return "zero"; }
 
int main()
{
  std::cout << f<1>() << std::endl;
  std::cout << f<0>() << std::endl;
  return 0;
}