M.Hiroi's Home Page

Linux Programming

お気楽C++プログラミング超入門

[ PrevPage | C++ | NextPage ]

テンプレートの基礎知識

初心者 (M.Hiroi を含む) にとって、C++は大変難しいプログラミング言語ですが、そのなかでも M.Hiroi が興味を持っている機能に「テンプレート (template)」があります。最初にテンプレートを見たときは、ただでさえ複雑なC++に新しい機能がまた追加されるのか、とうんざりしたものです。ところが、実際に試してみると、これがとても便利な機能なのです。

一般的なオブジェクト指向言語は「クラス (class)」が中心で、再利用性を高めるためにもクラスの設計がとても重要になります。ところが、初心者にとってクラスを適切に設計することは大変荷の重い作業です。これにC++特有の複雑な機能が加わるため、C++のメリットを実感できない方もいるのではないでしょうか。

テンプレートの場合、クラスの設計といった難しいことをあまり意識しなくても、プログラムの再利用が可能になります。それに、テンプレートの基本的な機能はそれほど難しくはないので、M.Hiroi のようなC++初心者でもその恩恵を受けることができるのではないか、と思っております。

このように書くと、エキスパートの方から怒られてしまうかもしれませんね。テンプレートは高度で複雑な機能なので、難しいところがあるのは事実です。ですが、基本的な機能であれば、初心者でも理解することができますし、標準ライブラリ (Standard Template Library : STL) も簡単に使うことができるようになります。とりあえず難しいところはおいといて、基本的なところからゆっくりと勉強していきましょう。

●テンプレートの定義

テンプレートの基本的な考え方は簡単です。クラスや関数の定義するとき、メンバ変数や関数の引数 (返り値) にデータ型を指定しますが、これをパラメータ化できるようにしたものがテンプレートです。テンプレートを定義するとき、関数の仮引数のようにデータ型を受け取るパラメータを指定します。これを「テンプレートパラメータ」とか「テンプレート仮引数」と呼びます。

実際に使用するときはデータ型をテンプレートに渡します。関数を呼び出すときに渡す実引数のように、これを「テンプレート実引数」といいます。すると、そのデータ型に合わせたクラスや関数が生成されます。これにより、一つのプログラムでいろいろなデータ型に対応することが可能になります。[*1]

テンプレートの定義はキーワード template を使います。テンプレートの構文を示します。

  1. template <class 名前, ...> クラス定義;
  2. template <class 名前, ...> 関数定義

template の後ろの < > の中に "class 名前" を指定します。この名前がテンプレート仮引数になります。テンプレート仮引数は複数指定することができます。名前の前に class が付いていますが、クラスに限定する意味はなく、テンプレート実引数には int や double など基本的なデータ型も指定することができます。

class のほかに typename というキーワードを使うこともできます。基本的には、class と typename に大きな違いはないのですが、複雑なテンプレートを定義するとき typename が必要になる場合があります。とりあえず、本稿では class を使うことにしましょう。

また、class (or typename) のかわりに、int や double などのデータ型を指定することもできます。その場合、テンプレート実引数にはデータ型の値 (整数や浮動小数点数など) を渡すことになります。

1 のようにテンプレートで定義されたクラスを「クラステンプレート」といい、テンプレートから生成されたクラスを「テンプレートクラス」といいます。2 のようにテンプレートで定義された関数を「関数テンプレート」といい、テンプレートから生成された関数を「テンプレート関数」といいます。クラス定義や関数定義の中で、テンプレート仮引数はデータ型として使用することができます。

-- note --------
[*1] SML/NJ や OCaml などの関数型言語でも、データ型をパラメータ化することができます。これを「型変数」といい、いろいろなデータ型に対応できる関数のことを「多相型関数」といいます。これらの言語には「型推論」という機能があり、プログラマがデータ型を宣言する必要はほとんどありません。

●クラステンプレート

それでは実際にテンプレートでクラスを定義してみましょう。まず最初に、テンプレートを使わないでクラスを定義します。

リスト : クラス Foo の定義 (sample1700.cpp)

#include <iostream>
using namespace std;

class Foo {
  int x;
public:
  Foo() : x(0) {}
  Foo(int n) : x(n) {}
  int get_x() const { return x; }
  void put_x(int n) { x = n; }
};

int main()
{
  Foo a;
  cout << a.get_x() << endl;
  a.put_x(10);
  cout << a.get_x() << endl;
}
$ clang++ sample1700.cpp
$ ./a.out
0
10

クラス Foo は int 型の整数を格納します。テンプレートを使うと、int 型だけではなく他のデータ型を格納するクラスを簡単に作ることができます。次のリストを見てください。

リスト : クラステンプレート (sample1701.cpp)

#include <iostream>
using namespace std;

template<class T> class Foo {
  T x;
public:
  Foo() : x(T()) {}
  Foo(T n) : x(n) {}
  T get_x() const { return x; }
  void put_x(T n) { x = n; }
};

int main()
{
  Foo<int> a;
  cout << a.get_x() << endl;
  a.put_x(10);
  cout << a.get_x() << endl;
  Foo<double> b;
  cout << b.get_x() << endl;  
  b.put_x(1.2345);
  cout << b.get_x() << endl;
  Foo<string> c;
  cout << "[" << c.get_x() << "]" << endl;  
  c.put_x("hello, world");
  cout << c.get_x() << endl;
}

ここでは仮引数名を T としました。仮引数名は 1 文字にする必要はありません。わかりやすい名前を付けたほうが良いでしょう。一般的には、名前の先頭を英大文字にすることが多いようです。

仮引数名 T は Foo の中で参照することができます。具体的には、int のかわりに T を使ってプログラムを記述するだけです。メンバ変数の宣言は int x から T x に書き換えます。メンバ関数の定義も int get_x() を T get_x() に、void put_x(int n) を void put_x(T n) に書き換えます。

コンストラクタも同様に書き換えます。デフォルトコンストラクタの場合、T() と記述することでデータ型 T のデフォルトコンストラクタを呼び出して、メンバ変数 x を初期化することができます。これは基本的なデータ型でも大丈夫です。たとえば int() は 0 を返し、double() は 0.0 を返します。string() は空文字列を返します。

テンプレートから実際のクラスとインスタンスを生成する場合は次のように宣言します。

テンプレート名<テンプレート引数, ...> 変数名;
テンプレート名<テンプレート引数, ...> 変数名(引数, ...);

テンプレート名の後ろの < > の中にテンプレート実引数を指定します。データ型は "テンプレート名<テンプレート実引数, ...>" になります。テンプレートが同じでも、異なる実引数を与えれば、別のデータ型として扱われます。

main() で Foo<int> a; とすれば、変数 a に int を格納する Foo のインスタンスが生成されます。Foo<double> b; とすれば、変数 b に double を格納する Foo のインスタンスが生成され、Foo<string> c; とすれば、変数 c に string を格納する Foo のインスタンスが生成されます。

それでは実行してみましょう。

$ clang++ sample1701.cpp
$ ./a.out
0
10
0
1.2345
[]
hello, world

変数 a, b, c のインスタンスはデフォルトコンストラクタによって初期化されるので、メンバ変数の値は 0, 0.0, "" になります。そのあと、メンバ関数 put_x() で値を書き換え、その値を get_x() で取り出して表示します。このように、一つのテンプレートで複数のデータ型に対応するプログラムを作ることができます。

●関数テンプレート

次は関数テンプレートを作ってみましょう。簡単な例として、クイックソートを取り上げます。次のリストを見てください。

リスト : クイックソート (sample1702.cpp)

#include <iostream>
using namespace std;

template<class T>
void qsort_sub(T buff[], int low, int high)
{
  T pivot = buff[low + (high - low) / 2];
  int i = low;
  int j = high;
  while (true) {
    while (pivot > buff[i]) i++;
    while (pivot < buff[j]) j--;
    if (i >= j) break;
    T temp = buff[i];
    buff[i] = buff[j];
    buff[j] = temp;
    i++;
    j--;
  }
  if (low < i - 1) qsort_sub(buff, low, i - 1);
  if (high > j + 1) qsort_sub(buff, j + 1, high);
}

template<class T>
void quick_sort(T buff[], int size)
{
  qsort_sub(buff, 0, size - 1);
}

template<class T>
void print_buffer(T buff[], int size)
{
  for (int i = 0; i < size; i++)
    cout << buff[i] << " ";
  cout << endl;
}

int main()
{
  int a[10] = {5, 6, 4, 7, 3, 8, 2, 9, 1, 0};
  quick_sort(a, 10);
  print_buffer(a, 10);
  double b[10] = {5.5, 6.6, 4.4, 7.7, 3.3, 8.8, 2.2, 9.9, 1.1, 0};
  quick_sort(b, 10);
  print_buffer(b, 10);
}

関数をテンプレートで定義する場合も template から始まります。その次に、< > の中に 1 個以上のテンプレート仮引数を指定するのも同じです。あとは普通の関数と同じように定義します。関数を呼び出すときは関数名の後ろに < > を付けて、そこにテンプレート実引数を指定します。

関数名<テンプレート実引数, ...>(引数, ...);

ただし、関数に渡す引数のデータ型などによってテンプレート実引数のデータ型が決定できる場合、テンプレート実引数の指定を省略することができます。関数 quick_sort, qsort_sub, print_buffer は関数の仮引数 buff のデータ型が T であり、関数の実引数として与えられるデータ型から、テンプレート実引数として渡されるデータ型を決定することができます。この場合、テンプレート実引数を省略して、通常の関数のように呼び出すことができます。

main() で quick_sort を呼び出すところが 2 か所ありますが、最初の quick_sort は引数の型が int なので、パラメータ T を int に置き換えた関数が生成されます。次の quick_sort の場合は T を double に置き換えた関数が生成されます。C++は関数を多重定義できるので、このような芸当が可能になるのです。

それでは実行してみましょう。

$ clang++ sample1702.cpp
$ ./a.out
0 1 2 3 4 5 6 7 8 9
0 1.1 2.2 3.3 4.4 5.5 6.6 7.7 8.8 9.9

●Pair の作成

それでは簡単な例題として、2 つの異なるデータ型を格納する構造体 Pair をテンプレートで作成してみましょう。C++の構造体はクラスと同様にテンプレートを使って定義することができます。なお、C++の標準ライブラリには pair という同等の機能を持つテンプレートが用意されていますが、C++のお勉強ということで、実際にプログラムを作ってみましょう。

リスト : Pair テンプレート (sample1703.cpp)

#include <iostream>
using namespace std;

template<class T, class U> struct Pair {
  T fst;
  U snd;
  Pair() : fst(T()), snd(U()) { }
  Pair(T x, U y) : fst(x), snd(y) { }
  // 出力演算子の多重定義
  friend ostream& operator<<(ostream& output, const Pair& p) {
    output << "(" << p.fst << "," << p.snd << ")";
    return output;
  }
};

int main()
{
  Pair<string,int> a("hello", 10);
  cout << a << endl;
  cout << a.fst << endl;
  cout << a.snd << endl;
  a.fst = "world";
  a.snd = 20;
  cout << a << endl;
  Pair<int,int> b;
  cout << b << endl;
  b.fst = 1;
  b.snd = 2;
  cout << b << endl;
  Pair<string, int> c[4] = {
    {string("foo"), 1},
    {string("bar"), 2},
    {string("baz"), 3},
    {string("oops"), 4}
  };
  for (auto x : c) cout << x << endl;
}

Pair には 2 つの異なるデータ型を格納するので、テンプレート仮引数は T, U の 2 つが必要になります。メンバ変数 fst が T 型のデータを格納し、snd が U 型のデータを格納します。C++の構造体はデフォルトのアクセス制御が public のクラスとほぼ同じです。クラスと同様に、構造体の中でコンストラクタなどのメンバ関数や friend 関数を定義することができます。メンバ変数は public なのでアクセス関数は定義していません。

なお、テンプレートで friend 関数を定義する場合、たとえば出力演算子をクラスの中で定義するのは簡単ですが、クラスの外側で定義するときには注意が必要です。これはあとで説明します。

main() の中で、Pair<string,int> とすると、string と int を格納するインスタンスが生成されます。T と U には同じデータ型を指定してもかまいません。Pair<int,int> とすると、int と int を格納するインスタンスが生成されます。配列に格納する場合、初期値を指定しなければデフォルトコンストラクタで初期化されます。

テンプレートの型指定は複雑になりがちです。最近の規格 (C++11) では、データ型に auto を指定すると、コンパイラがデータ型を推論してくれるようになりました。最後の for 文では、auto を使わないと変数 x のデータ型 Pair<string,int> を記述しないといけませんが、auto を使えばコンパイラが決めてくれるので簡単です。

実行結果は次のようになります。

$ clang++ sample1703.cpp
$ ./a.out
(hello,10)
hello
10
(world,20)
(0,0)
(1,2)
(foo,1)
(bar,2)
(baz,3)
(oops,4)

●テンプレートのデフォルト引数

テンプレート仮引数には関数のデフォルト引数のようにデフォルトの値を設定することができます。

template <class T = type, ...>

仮引数名の後ろに = を付けて、その後ろにデフォルトのデータ型 (または値) を指定します。

それでは簡単な例題として、テンプレートでスタッククラス Stack を作ってみましょう。次のリストを見てください。

リスト : スタッククラス (sample1704.cpp)

#include <iostream>
#include <stdexcept>
using namespace std;

template<class T = int, int N = 8> class Stack {
  T *buff;
  int sp;
  // コピーコンストラクタと代入演算子の無効化
  Stack(const Stack&);
  Stack& operator=(const Stack&); 
public:
  Stack() : buff(new T [N]), sp(0) { }
  ~Stack() { delete[] buff; }
  bool empty() const { return !sp; }
  bool full() const { return sp == N; }
  void push(T x) {
    if (sp >= N) throw runtime_error("Stack::push overflow");
    buff[sp++] = x;
  }
  T pop() {
    if (sp == 0) throw runtime_error("Stack::pop underfolw");
    return buff[--sp];
  }
};

int main()
{
  Stack<> a;
  for (int i = 0; i < 8; i++) a.push(i);
  while (!a.empty()) cout << a.pop() << " ";
  cout << endl;
  Stack<double, 4> b;
  for (int i = 0; i < 4; i++) b.push(1.2345 + i);
  while (!b.empty()) cout << b.pop() << " ";
  cout << endl;
}
$ clang++ sample1704.cpp
$ ./a.out
7 6 5 4 3 2 1 0
4.2345 3.2345 2.2345 1.2345

Stack はテンプレート仮引数に T と N を受け取ります。T がスタックに格納するデータ型、N がスタックの大きさです。どちらの引数もデフォルト値が設定されていて、T は int で N が 8 になります。テンプレート引数に整数値が指定されているので、整数値が異なるとデータ型は別なものになります。つまり、Stack<int, 8> と Stack<int, 9> は異なるデータ型として扱われます。ご注意くださいませ。

なお、今回は簡単な例題なので、コピーコンストラクタと代入演算子を無効化しています。これはプロトタイプを private で宣言するだけです。コピーコンストラクタや代入演算子が必要になる処理はコンパイルでエラーとなります。

メンバ関数は full(), empty(), push(), pop() の 4 つを用意しました。この中でテンプレート仮引数 N を参照することができます。なお、N に渡すことができるのは整数 (または定数) だけで、変数の値を渡すことはできません。Stack は 2 つの引数がありますが、どちらも省略する場合は Stack<> のように <> を付けてください。<> を省略することはできません。

●テンプレートの特殊化

テンプレートはとても便利な機能ですが、特定のデータ型だけ特別な処理を行いたい場合もあるでしょう。このような場合、汎用的なテンプレートを定義したあと、データ型を指定した特別なテンプレートを定義することで対応することができます。これを「テンプレートの特殊化」といいます。

テンプレートの特殊化は次のように定義します。

template<class T, ...> class Foo { ... };   // 汎用
template<> class Foo<type, ...> { ... };    // type 専用

最初に汎用のクラステンプレート Foo を定義します。テンプレートを特殊化する場合は、template で指定する仮引数を省略して <> だけを指定し、クラス名の後ろで特定のデータ型を < > の中で指定します。

簡単な例を示しましょう。次のリストを見てください。

リスト : テンプレートの特殊化 (sample1705.cpp)

#include <iostream>
#include <iomanip>
using namespace std;

template<class T> class Foo {
  T x;
public:
  Foo() : x(T()) {}
  Foo(T n) : x(n) {}
  void print() const { cout << x << endl; }
};

template<> class Foo<double> {
  double x;
public:
  Foo() : x(0) {}
  Foo(double n) : x(n) {}
  void print() const {
    cout << setprecision(16) << x << endl;
  }
};

int main()
{
  Foo<int> a(123);
  Foo<double> b(1.11111111 * 1.11111111);
  Foo<string> c("hello, world");
  a.print();
  b.print();
  c.print();
}
$ clang++ sample1705.cpp
$ ./a.out
123
1.234567898765432
hello, world

クラス Foo はデータ型 T を格納し、メンバ関数 print でそれを表示します。print は標準的な方法でデータを表示しますが、double だけ小数点数の桁を増やしたい場合、double 専用のテンプレートを作成します。template<> class Foo<double> とすれば、double に特化したテンプレートになります。あとは、Foo<double> b; と宣言すれば、特殊化したテンプレートが使用されます。それ以外のデータ型は汎用のテンプレートが使用されます。

●テンプレートの部分特殊化

テンプレートの一部分だけを特殊化することもできます。たとえば、テンプレート仮引数が複数ある場合、一部の仮引数を特定のデータ型に指定することができます。次の例を見てください。

リスト : テンプレートの部分特殊化 (sample1706.cpp)

#include <iostream>
#include <iomanip>
using namespace std;

template<class T, class U> struct Pair {
  T fst;
  U snd;
  Pair() : fst(T()), snd(U()) { }
  Pair(T x, U y) : fst(x), snd(y) { }
  // 出力演算子の多重定義
  friend ostream& operator<<(ostream& output, const Pair& p) {
    output << "(" << p.fst << "," << p.snd << ")";
    return output;
  }
};

// 部分特殊化
template<class T> struct Pair<T, double> {
  T fst;
  double snd;
  Pair() : fst(T()), snd(0) { }
  Pair(T x, double y) : fst(x), snd(y) { }
  // 出力演算子の多重定義
  friend ostream& operator<<(ostream& output, const Pair& p) {
    output << "(" << p.fst << "," << setprecision(16) << p.snd << ")";
    return output;
  }
};

int main()
{
  Pair<string,double> a("hello", 1.11111111 * 1.11111111);
  cout << a << endl;
  Pair<int,double> b(123, 1.11111111 * 1.11111111);
  cout << b << endl;
}
$ clang++ sample1706.cpp
$ ./a.out
(hello,1.234567898765432)
(123,1.234567898765432)

Pair のメンバ変数 snd のデータ型を double に特定します。この場合、テンプレート仮引数は fst のデータ型 T だけ指定して、Pair の後ろでは <T, double> とします。これで、Pair<string, double> a; や Pair<int, double> b; とすると、特殊化したテンプレートが使用されます。

また、次のようにポインタ型だけ特殊化することも可能です。

リスト : ポインタ型の特殊化 (sample1707.cpp)

#include <iostream>
using namespace std;

template<class T> class Foo {
  T x;
public:
  Foo() : x(T()) {}
  Foo(T n) : x(n) {}
  void print() const { cout << x << endl; }
};

template<class T> class Foo<T*> {
  T* x;
public:
  Foo(T* n) : x(n) {}
  void print() const { cout << *x << endl; }
};

int main()
{
  Foo<int> a(123);
  a.print();
  int x = 10;
  Foo<int*> b(&x);
  b.print();
}
$ clang++ sample1707.cpp
$ ./a.out
123
10

template<class T> class Foo の後ろで <T*> を指定すると、ポインタ型 T* だけを特殊化することができます。たとえば、Foo<int> は汎用のテンプレートが使用されますが、Foo<int*> は特殊化したテンプレートが使用されます。

●テンプレート関数の特殊化

テンプレートクラスと同様に、テンプレート関数も特殊化することができます。

template<class T, ...> 返り値の型 関数名(引数, ...) { 本体 }    // 汎用
template<> 返り値の型 関数名<type, ...>(引数, ...) { 本体 }     // type 専用

最初に汎用の関数テンプレート Foo を定義します。テンプレートを特殊化する場合は、template で指定する仮引数を省略して <> だけを指定し、関数名の後ろで特定のデータ型を < > の中で指定します。なお、関数の引数のデータ型などで type を特定できる場合は、関数名の後ろの <type, ... > を省略することができます。

たとえば、関数テンプレートで取り上げたクイックソートですが、Cスタイル文字列 (const char*) はアドレスを比較してしまうので、ソートすることができません。この場合、次のようにテンプレート関数を特殊化します。。

リスト : テンプレート関数の特殊化 (sample1708.cpp)

#include <iostream>
#include <cstring>
using namespace std;

// 汎用
template<class T>
void qsort_sub(T buff[], int low, int high)
{
  T pivot = buff[low + (high - low) / 2];
  int i = low;
  int j = high;
  while (true) {
    while (pivot > buff[i]) i++;
    while (pivot < buff[j]) j--;
    if (i >= j) break;
    T temp = buff[i];
    buff[i] = buff[j];
    buff[j] = temp;
    i++;
    j--;
  }
  if (low < i - 1) qsort_sub(buff, low, i - 1);
  if (high > j + 1) qsort_sub(buff, j + 1, high);
}

template<class T>
void quick_sort(T buff[], int size)
{
  qsort_sub(buff, 0, size - 1);
}

template<class T>
void print_buffer(T buff[], int size)
{
  for (int i = 0; i < size; i++)
    cout << buff[i] << " ";
  cout << endl;
}

// 特殊化
template<>
void qsort_sub(const char* buff[], int low, int high)
{
  const char* pivot = buff[low + (high - low) / 2];
  int i = low;
  int j = high;
  while (true) {
    while (strcmp(pivot, buff[i]) > 0) i++;
    while (strcmp(pivot, buff[j]) < 0) j--;
    if (i >= j) break;
    const char* temp = buff[i];
    buff[i] = buff[j];
    buff[j] = temp;
    i++;
    j--;
  }
  if (low < i - 1) qsort_sub(buff, low, i - 1);
  if (high > j + 1) qsort_sub(buff, j + 1, high);
}

template<>
void quick_sort(const char* buff[], int size)
{
  qsort_sub(buff, 0, size - 1);
}

template<>
void print_buffer(const char* buff[], int size)
{
  for (int i = 0; i < size; i++)
    cout << buff[i] << " ";
  cout << endl;
}

int main()
{
  int a[10] = {5, 6, 4, 7, 3, 8, 2, 9, 1, 0};
  quick_sort(a, 10);
  print_buffer(a, 10);
  const char* b[5] = {
    "foo", "baz", "bar","oops", "abc"
  };
  quick_sort(b, 5);
  print_buffer(b, 5);
}

Cスタイル文字列は標準ライブラリ <cstring> の関数 strcmp() で比較することができます。これはC言語の標準ライブラリ string.h の関数 strcmp() と同じものです。関数 qsort_sub(), quick_sort(), print_buffer() の特殊化は、引数のデータ型から const char* が特定できるので、関数名の後ろにデータ型を指定する必要はありません。あとは、汎用バージョンのパラメータ T のかわりに const char* を指定するだけです。

実行結果は次のようになります。

$ clang++ sample1708.cpp
$ ./a.out
0 1 2 3 4 5 6 7 8 9
abc bar baz foo oops

●テンプレートと高階関数

テンプレートを使うと、いろいろなデータ型に対応する高階関数を簡単に作成することができます。たとえば、配列の要素に関数を適用する高階関数 for_each は関数テンプレートを使って次のように定義することができます。

リスト : 高階関数 for_each (テンプレート版)

template<class T, class F>
void for_each(T buff[], int size, F func)
{
  for (int i = 0; i < size; i++)
    func(buff[i]);
}

テンプレート仮引数 T が配列のデータ型、F が関数の型を表します。for ループで配列の要素を順番に取り出して、関数 func に渡して呼び出すだけです。for_each に渡す関数もテンプレートで定義することができます。

リスト : データの表示

template<class T>
void print(T x) { cout << x << " "; }

関数 print はデータ型 T の値を画面に表示します。for_each に渡すときは、関数名の後ろに <データ型> を指定して、テンプレート関数を生成してください。

それでは実行してみましょう。

リスト : for_each の実行例 (sample1709.cpp)

int main()
{
  int a[8] = {0,1,2,3,4,5,6,7};
  double b[5] = {1.1, 2.2, 3.3, 4.4, 5.5};
  string c[4] = {
    "foo", "bar", "baz", "oops"
  };
  for_each(a, 8, print<int>);
  cout << endl;
  for_each(b, 5, print<double>);
  cout << endl;
  for_each(c, 4, print<string>);
  cout << endl;
}
$ clang++ sample1709.cpp
$ ./a.out
0 1 2 3 4 5 6 7
1.1 2.2 3.3 4.4 5.5
foo bar baz oops

正常に動作していますね。ところで、関数呼び出しができるのは、関数ポインタだけではなく、関数オブジェクトでも可能です。つまり、for_each には関数オブジェクトも渡すことができるのです。次の例を見てください。

リスト : 関数オブジェクトを for_each に渡す (sample1710.cpp)

#include <iostream>
using namespace std;

template<class T, class F>
void for_each(T buff[], int size, F func)
{
  for (int i = 0; i < size; i++)
    func(buff[i]);
}

// クラステンプレート
template<class T> class Display {
public:
  void operator()(T x) { cout << x << " "; }
};
  
int main()
{
  int a[8] = {0,1,2,3,4,5,6,7};
  double b[5] = {1.1, 2.2, 3.3, 4.4, 5.5};
  string c[4] = {
    "foo", "bar", "baz", "oops"
  };
  for_each(a, 8, Display<int>());
  cout << endl;
  for_each(b, 5, Display<double>());
  cout << endl;
  for_each(c, 4, Display<string>());
  cout << endl;
}
$ clang++ sample1710.cpp
$ ./a.out
0 1 2 3 4 5 6 7
1.1 2.2 3.3 4.4 5.5
foo bar baz oops

Display はクラステンプレートです。関数呼び出し演算子 () を定義しているので、Display のインスタンスは関数オブジェクトになります。for_each に渡すときには、Display の後ろに <データ型>() を指定してインスタンスを生成してください。

●イテレータと高階関数

イテレータを使うと、より汎用的な高階関数を作成することができます。たとえば、for_each は次のように定義することができます。

リスト : for_each (イテレータ版)

template<class T, class F>
void for_each(T buff, F func)
{
  for (auto iter = buff.begin(); iter != buff.end(); ++iter)
    func(*iter);
}

イテレータのデータ型は auto を使うと簡単に指定することができます。コンパイルするときはオプション -std=c++11 を付けることを忘れないでください。

固定長の配列でイテレータを使用する場合は、標準ライブラリ <array> が便利です。array はクラステンプレートで、テンプレート仮引数にはデータ型と大きさを指定します。簡単な使用例を示します。

リスト : array と for_each の使用例 (sample1711.cpp)

#include <iostream>
#include <array>
using namespace std;

template<class T, class F>
void for_each(T buff, F func)
{
  for (auto iter = buff.begin(); iter != buff.end(); ++iter)
    func(*iter);
}

template<class T>
void print(T x) { cout << x << " "; }

template<class T> class Display {
public:
  void operator()(T x) { cout << x << " "; }
};
  
int main()
{
  array<int, 8> a = {0,1,2,3,4,5,6,7};
  array<double, 5> b = {1.1, 2.2, 3.3, 4.4, 5.5};
  array<string, 4> c = {
    "foo", "bar", "baz", "oops"
  };
  for_each(a, print<int>);
  cout << endl;
  for_each(b, print<double>);
  cout << endl;
  for_each(c, print<string>);
  cout << endl;
  for_each(a, Display<int>());
  cout << endl;
  for_each(b, Display<double>());
  cout << endl;
  for_each(c, Display<string>());
  cout << endl;
}
$ clang++ sample1711.cpp
$ ./a.out
0 1 2 3 4 5 6 7
1.1 2.2 3.3 4.4 5.5
foo bar baz oops
0 1 2 3 4 5 6 7
1.1 2.2 3.3 4.4 5.5
foo bar baz oops

なお、C++の標準ライブラリ algorithm に定義されている for_each は、引数にイテレータを渡すように定義されています。

リスト : 標準ライブラリ関数 for_each の定義例

template<class It, class Fn>
F for_each(It first, It last, Fn f)
{
  for (; first != last; ++first)
    f(*first);
  return f;
}

C++の場合、イテレータはポインタと同じような文法でコンテナの要素にアクセスできるように設計されています。イテレータを引数に受け取る関数は、ポインタを渡してもたいていの場合は動作します。たとえば、配列にイテレータは用意されていませんが、ポインタを渡せば for_each でも動作します。次の例を見てください。

リスト : for_each の使用例 (sample1712.cpp)

#include <iostream>
#include <algorithm>
using namespace std;

void print_int(int x) { cout << x << " "; }

int main()
{
  int a[8] = {1,2,3,4,5,6,7,8};
  for_each(a, a + 8, print_int);
  cout << endl;
}
$ clang++ sample1712.cpp
$ ./a.out
1 2 3 4 5 6 7 8

このように、配列 a の先頭アドレス a と終端のアドレス a + 8 を for_each に渡して、関数 print_int() を呼び出すことができます。


初版 2015 年 9 月 23 日
改訂 2023 年 4 月 9 日

Copyright (C) 2015-2023 Makoto Hiroi
All rights reserved.

[ PrevPage | C++ | NextPage ]