C++ 発展編 5日目

テンプレート

ジェネリックプログラミングとは、特定のデータ型に限定されず、様々なデータ型に対して機能するコードを記述する仕組みです
例えば、整数をソートする関数、浮動小数点数をソートする関数、文字列をソートする関数をそれぞれ個別に実装する代わりに、任意の型をソートできる単一のソート関数を記述するようなことが実現できます

C++では、同じロジックを異なるデータ型に対して再利用できるこの「ジェネリックプログラミング」を実現する機能としてテンプレートがあります

C++には主に「関数テンプレート」と「クラステンプレート」の2種類があります

関数テンプレート

関数テンプレートは、異なるデータ型に対して同じ処理を行う関数を定義する際に使用します

関数テンプレートの定義・宣言は以下のように記述します

template <typename テンプレートパラメータ>
戻り値型 関数名(引数){

}

関数テンプレートを定義するには、templateキーワードと型パラメーターを<>で囲んだものを関数の前に書きます
型パラメーターは、typenameまたはclassと任意の名前のテンプレートパラメータで指定できます
関数テンプレートを呼び出すときは、型パラメーターに具体的な型を指定するか、コンパイラに推測させることができます

例えば、以下は、2つの値を比較して大きい方を返すmax関数のサンプルプログラムです
整数型、浮動小数点型、文字列型など、様々な型に対して機能するmax関数を、テンプレートを使用することで一つにまとめることができます

template <typename T>
T max(T a, T b) {
    return (a > b) ? a : b;
}

// 使用例
int i = max(10, 20);      // Tはintに推論される
double d = max(3.14, 2.71); // Tはdoubleに推論される
std::string s = max(std::string("apple"), std::string("banana")); // Tはstd::stringに推論される

上記の例では、typename Tがプレースホルダとして機能し、関数呼び出し時に実際の型(int、double、std::stringなど)に置き換わります

クラステンプレート

クラステンプレートは、異なるデータ型を扱うことができるクラスを定義する際に使用します

template <typename テンプレートパラメータ>
class クラス名{
  クラス本体
}

クラステンプレートを定義するには、templateキーワードと型パラメーターを<>で囲んだものをクラスの前に書きます
クラステンプレートのインスタンスを作るときは、型パラメーターに具体的な型を指定する必要があります

以下は、任意の型の要素を格納できるスタックやキューのようなデータ構造を実装したサンプルプログラムです

template <typename T>
class MyStack {
public:
    void push(T item) { /* ... */ }
    T pop() { /* ... */ }
    // ...
private:
    std::vector<T> elements;
};

// 使用例
MyStack<int> intStack;         // int型のスタック
MyStack<std::string> stringStack; // std::string型のスタック

このサンプルプログラムでは、MyStackクラスが定義され、Tがスタックに格納される要素の型を決定しています

以下は、配列を表すクラステンプレートを定義したサンプルプログラムです
typenameのあとに、いくつものパラメータを渡すことができます

// クラステンプレートの定義
template <typename T, int N>
class Array {
private:
  T* ptr;
  int size;
public:
  Array(T arr[], int s); // コンストラクタ
  void print(); // 配列の要素を表示するメソッド
};

// クラステンプレートの実装
template <typename T, int N>
Array<T, N>::Array(T arr[], int s) {
  ptr = new T[N];
  size = s;
  for (int i = 0; i < size; i++) {
    ptr[i] = arr[i];
  }
}

template <typename T, int N>
void Array<T, N>::print() {
  for (int i = 0; i < size; i++) {
    std::cout << " " << *(ptr + i);
  }
  std::cout << std::endl;
}

// クラステンプレートのインスタンス化
int arr[5] = {1, 2, 3, 4, 5};
Array<int, 5> a(arr, 5); // 型と値を指定
a.print();

typename Tは、Arrayクラスが扱う要素の型(intやdouble、std::stringなど)を指定するためのテンプレートパラメータです
int N は、Arrayクラスが保持する配列のサイズをコンパイル時に決定するための非型テンプレートパラメータです
Nには整数値(5など)が渡されます
このように、テンプレート宣言には、必要に応じて型パラメータと非型テンプレートパラメータを任意の数だけ記述できます

可変長テンプレート

可変長テンプレートは、任意の数の型や値をパラメーターとして受け取るテンプレートです
可変長テンプレートを定義するには、typenameなどの型パラメーターの後に省略記号…を付けます

template <typename... 型パラメータパック>
戻り値型 関数名(型パラメータパック... args){

}

パラメータパックには、複数の型またはオブジェクトがまとめられた状態となっているため、再帰的なテンプレートの展開やパックの展開などのテクニックを使って処理する必要があります

参考URL:可変引数テンプレート [N2242] – cpprefjp C++日本語リファレンス

以下は、可変長の引数を受け取りその合計を返すサンプルプログラムです

#include <iostream>

// 可変長テンプレートの定義
template <typename T>
T sum(T t) {
   return t;
}

template <typename T, typename... Args>
T sum(T t, Args... args) {
  return t + sum(args...);
}

int main(){
    // 可変長テンプレートの呼び出し
    int s = sum(1, 2, 3, 4, 5); // 15
    std::cout << s << std::endl;

    double d = sum(1.5, 2.5, 3.5, 5); // 12.5
    std::cout << d << std::endl;

    return 0;
}

T sum()関数をオーバーロードで定義しています
T sum(T t, Args... args)
これは再帰ケース (Recursive Case) と呼ばれるものです
Args… args は、0個以上の追加の引数(可変長引数パック)を表します
この関数は、最初の引数 t と、残りの引数 args… を使った sum 関数の再帰呼び出しの結果を足し合わせます

T sum(T t)
これは基底ケース (Base Case) と呼ばれるものです
引数が1つだけになったときに呼び出されます
再帰呼び出しの終端を定義しており、これがないとコンパイルエラーになるか、無限再帰に陥ります

Args… が展開できる(つまり、まだ複数の引数がある)間は再帰ケースの sum 関数が呼び出され続け、引数が1つになり Args… が展開できない状態になると、基底ケースの sum 関数が呼び出されて再帰が終了します

コメント

この記事へのコメントはありません。

関連記事

C++ 基礎編 5日目

C言語 応用編 ~4日目~

Java 応用編 1日目

PAGE TOP