プログラミングの魔物

エラー、バグ、仕様変更と戦うブログ

C++テンプレートテクニック12章 C++0xにおけるテンプレート

C++0xにおけるテンプレート

C++03の問題点を改善したバージョン。

  • 連続した山括弧の問題改善(テンプレートの入れ子を書く時に演算子>>と評価されてしまう問題への対策)
  • 関数テンプレートにおけるデフォルトテンプレート引数の拡張
  • ローカル型と無名型をテンプレート引数として使用可能
  • TemplateAliases(typedefにテンプレートを使用できない問題に対し、usingキーワードで型の別名を付けられるように)
  • 定数式(今までコンパイル時に評価される式はマクロやメタ関数でしか作れなかったが、constexprキーワードによってコンパイル時に評価される「定数式」を書けるように)
  • コンパイル時アサート(コンパイル時のエラーのメッセージを指定できるように)
  • 可変引数テンプレート(型安全な可変引数を扱えるように)
  • 初期化子リスト({}でユーザー定義クラスを初期化)
  • 型推論(auto、decltype)
  • コンセプト(テンプレート型の制約を定義)

連続した山括弧の問題改善

vector<basic_string<char>> v;

関数テンプレートにおけるデフォルトテンプレート引数の拡張

template <typename T = double>
void foo(){
  T value;
}

ローカル型と無名型をテンプレート引数として使用可能

struct{}noname;
int main(){
  struct local{}a;
  foo(a);
  foo(noname);
}

TemplateAliases

template <typename T>
using Vec = vector<T>;
Vec<int> v;

また、既存の方を返すメタ関数をTemplateAliasesでラップすることでメタ関数の戻り値の方を省略できる。

template <typename T>
struct Vec_{
  typedef vector<T> type;
};
template <typename T>
using Vec = Vec_<T>::type;
Vec_<int>::type v;  //c++03
Vec<int> v;  //c++0x

定数式

//C++03マクロ
#define MAX(x,y) ((x)>(y) ? (x):(y))
int ar[MAX(1,2)];
//C++03メタ関数
template <int X, int Y>
struct max{
  static const int value = X > Y ? X : Y;
};
int ar[max<1,2>::value];
//C++0x constexpr
constexpr max(int x, int y){
  return x > y ? x : y;
}
int ar[max(1,2)];

マクロやメタ関数を書く機会が減る!

コンパイル時アサート

template <typename T>
void foo(const T& value){
  static_assert(sizeof(T) >= sizeof(double), "doubleより小さいサイズ!");
}
int main(){
  foo(3.14);
  foo(3);//エラー。dobuleより小さいサイズ!
}

可変引数テンプレート

void printf(const char* s){
  while(*s){
    if (*s == '%' && *++s != '%') throw std::runtime_error("不正なフォーマット文字列");
    std::cout << *s++;
  }
}
template <typename T, typename... Args>
void printf(const char* s, const T& value, const Args&... args){
  while(*s){
    if (*s == '%' && *++s != '%'){
      std::cout << value;
      return printf(++s, args...);
    }
    std::cout << *s++;
  }
  throw std::runtime_error("余分な引数が渡された");
}
int main(){
  string s = "abcde";
  int n = 123;
  printf("%s,%d", s, n);
}
//可変引数テンプレートをクラステンプレートにも適用
template <typename>
class function;
template <typename R, typename... Args>
class function<R(Args...)>{
public:
  typedef R result_type;
  template <typename F>
  function(F);
  R operator()(Args...) const;
};
int main(){
  function<void(int)> f1;
  function<void(int, string)> f2;
  function<void(int, string, double)> f3;
}
//可変引数のパラメータ数を取得
template <typename ...Args>
void foo(const Args&...){
  cout << sizeof...(Args) << endl;
}
int main(){
  foo(1,"a",3.14);//「3」と出力
}

初期化子リスト

vector<int> v = {1,2,3};
map<string, int> m = {{"Akira",23},{"Johnny",38},{"Millia",16}};

型推論

vector<int> v;
auto a = v;
int a = 3, b = 2;
decltype(a+b) c = a + b;  //int c = a + b;
int f();
decltype(f()) n = 3;  //int n = 3;
//
template <typename Iterator>
void something(Iterator first, Iterator last){
  decltype(*first) value = *first;//↓の場合はint& value = *first;
}
vector<int> v;
something(v.begin(), v.end());

コンセプト

auto concept LessThanComparable<typename T>{
  bool operator < (const T&, const T&);
}
template <LessThanComparable T>
const T& min(const T& x, const T& y){
  return x < y ? x : y;
}
//複数のコンセプト
template <typename T>
requires LessThanComparable<T>&&CopyConstructible<T>
T min(T x, T y){
  return x < y ? x : y;
}
//コンセプトによるオーバーロードでタグ・ディスパッチしなくても済むようになる
template <InputIterator Iter> void advance(Iterator& it, Iter::difference_type n);
template <BidirectionalIterator Iter> void advance(Iterator& it, Iter::difference_type n);
template <RandomAccessIterator Iter> void advance(Iterator& it, Iter::difference_type n);

☆SFINAEによる型の判別や型特性も同じ理由から多くの場合必要なくなる。

コンセプトマップ(コンセプトの特殊化)

auto concept RandomAccessIterator<typename T>{
  typename value_type = T::value_type;//このままだとイテレーターにポインタを指定できない
}
template <typename T>
concept_map RandomAccessIterator<T*>{
  typedef T value_type;//concept_mapキーワードで特殊化。ポインタを受け取ったらTをvalue_typeとして割り当てる
}

Range-base for文

vector<int> v = {1,2,3};
for(int i : v){
  cout << i << endl;
}

配置挿入

struct something{
  something(int, int, int);
};
vector<something> v;
v.push_back(something(1,2,3));//コピーのコストが発生
//配置挿入を使うと
v.emplace_back(1,2,3);//内部でsomething(1,2,3)が呼ばれる

参考:

C++テンプレートテクニック

C++テンプレートテクニック