2011年11月12日土曜日

配列とテンプレート関数

g++のバージョンは4.5,vc++は2010.

C/C++で配列を引数にとるには,ポインタで受ける方法と参照で受ける方法の2つがある.
なので,次のコードは関数のオーバーロードがあいまいという理由でコンパイル時にエラーになる.

#include <iostream>

void f(int * p){
  std::cout << "pointer version" << std::endl;
}

void f(int (&a)[2]){
  std::cout << "array version" << std::endl;
}

int main(){
  int * p;
  f(p); // ポインタ版が呼ばれる.

  int a[2];
  f(a); // これがNG.オーバーロードがあいまい.
}

これをテンプレート関数にしてみるとどうだろう?
普通に考えると同じくエラーになりそうなものだけど,g++ではコンパイルOKになる.
理由はよくわからない.

template<class T>
void f(T * p){
 std::cout << "pointer version" << std::endl;
}

template<class T,std::size_t N>
void f(T (&a)[N]){
 std::cout << "array version" << std::endl;
}

/* main関数は略 */

さらに,1つ目の関数の引数からポインタ記号を外して,"void f(T p)~"とすると(汎用型の関数になってしまうけど),vc++でもコンパイルできる.
「T=非ポインタ型」で成立するテンプレート関数と「T=ポインタ型」で成立するテンプレート関数がある場合は前者が優先されるようだ.
ということで,g++でもvc++でも通用するコードを書くとするなら,boost.type_traitsを利用するのがよさそうだ.

#include <iostream>

#include <boost/utility/enable_if.hpp>
#include <boost/type_traits/is_pointer.hpp>

template<class T>
typename boost::disable_if<boost::is_pointer<T>,void>::type f(const T & v){
  std::cout << "general version" << std::endl;
}

template<class T>
typename boost::enable_if<boost::is_pointer<T>,void>::type f(const T p){
  std::cout << "pointer version" << std::endl;
}

template<class T,std::size_t N> void f(const T (&a)[N]){
  std::cout << "array version" << std::endl;
}

int main(){
  int v;
  f(v); // 汎用版

  int * p;
  f(p); // ポインタ版

  int a[2];
  f(a); // 配列版
}

もちろん,部分特殊化+静的メンバ関数で同じことができるけど,こっちの方が読みやすい…ような気がする.

0 件のコメント:

コメントを投稿