C++入門|同じ処理をまとめるテンプレート

同じ技を何度も書かず、型に合わせて自在に使う。C++のテンプレートは、修行コードをスマートにまとめる万能カプセルです。

C++でプログラムを書いていると、処理の内容はほとんど同じなのに、扱う型が違うだけで別々の関数を用意しなければならない場面があります。

たとえば、整数同士を足し合わせる処理と、文字列同士をつなげる処理を考えてみましょう。どちらも「2つの値を受け取り、+でまとめて、結果を返す」という流れは同じです。しかし、整数の場合の+は数値の加算を表し、文字列の場合の+は文字列の連結を表します。

C++は型を厳密に扱う言語なので、同じような見た目の処理でも、型が違えば別の関数として定義しなければならないことがあります。そこで登場するのが、テンプレートです。

テンプレートを使うと、型の部分をあとから差し替えられるようになります。つまり、処理の流れは1つにまとめたまま、int型として使ったり、string型として使ったりできるようになります。

ドラゴンボール風にたとえるなら、戦士ごとに別々の修行装置を作るのではなく、入ってきた戦士の流派や能力に合わせて自動調整される万能修行カプセルを用意するようなものです。

亀仙流の戦士が入れば亀仙流向けに、神流の戦士が入れば神流向けに、界王流の戦士が入れば界王流向けに調整されます。テンプレートもそれと同じように、使う型に合わせて関数の形を変えてくれます。

テンプレートとは何か

テンプレートとは、型をあとから当てはめて使えるひな型のことです。

通常の関数では、引数や戻り値の型を具体的に決めておく必要があります。int型を受け取る関数ならint型用、string型を受け取る関数ならstring型用として作ります。

しかし、処理の内容がほとんど同じ場合、型ごとに何度も似た関数を書くのは少し大変です。修正が必要になったときにも、複数の関数を確認しなければならず、書き間違いや修正漏れの原因になります。

テンプレートを使うと、型の部分をTのような仮の名前で表せます。そして、関数を呼び出すときに、Tをintにしたり、stringにしたりできます。

考え方内容
通常の関数型を具体的に決めて関数を作る
テンプレート型をあとから決められる関数のひな型を作る
Tあとから置き換えられる型を表す
関数テンプレート型が違っても同じ処理をまとめられる関数

この仕組みを使うことで、同じ処理を型ごとに何度も書かずに済むようになります。

型によって+の意味が変わる

C++では、+演算子は数値にも文字列にも使えます。ただし、型によって意味が変わります。

数値に対して+を使うと、足し算になります。

4 + 3 → 7

一方で、string型の文字列に対して+を使うと、文字列の連結になります。

"かめ" + "はめ波" → "かめはめ波"

見た目はどちらも+を使っていますが、行われている処理の意味は型によって変わります。

結果+の意味
int4 + 37数値の加算
string"かめ" + "はめ波""かめはめ波"文字列の連結

このように、同じ+でも、型によって動作が変わります。テンプレートは、このような「処理の形は同じだけれど、型が違う」という場面で力を発揮します。

図:型が違うと同じような処理でも別々に必要になる

この図が示していること

この図では、数値を足し合わせる処理と、文字列をつなげる処理の違いを表しています。

どちらも+を使っているため、見た目は似ています。しかし、int型では数値の加算になり、string型では文字列の連結になります。

C++では型を厳密に扱うため、型が違うと別々の処理として考える必要があります。テンプレートは、このような似た処理をまとめるために役立ちます。

テンプレートを使わない場合の書き方

まずは、テンプレートを使わずに、型ごとに関数を用意する書き方を見てみましょう。

このプログラムでは、修行ポイントを合計するunite関数と、技名を連結するunite関数を作ります。どちらも関数名はuniteですが、引数の型が違います。

プロジェクト/ファイル名: Chap6_01/main.cpp

#include <iostream>
#include <string>

using namespace std;

// 修行ポイントを合計する関数
int unite(int p1, int p2) {
    return p1 + p2;
}

// 技名を連結する関数
string unite(string t1, string t2) {
    return t1 + t2;
}

int main(int argc, char** argv) {
    // 整数同士の合計を表示する
    cout << unite(4, 3) << endl;

    // 文字列同士の連結を表示する
    cout << unite(string("かめ"), string("はめ波")) << endl;

    // プログラムが正常に終わったことを示す
    return 0;
}

実行結果

7
かめはめ波

このプログラムでは、2つのunite関数を定義しています。1つはint型の引数を2つ受け取り、数値を合計します。もう1つはstring型の引数を2つ受け取り、文字列を連結します。

同じuniteという名前を使えるのは、C++のオーバーロードの仕組みによるものです。引数の型が違うため、C++はどちらのuniteを呼び出せばよいか判断できます。

ただし、処理の流れをよく見ると、どちらもかなり似ています。2つの値を受け取り、+でまとめて、その結果を返しています。違っているのは、扱う型だけです。

このような関数がintとstringだけならまだよいですが、double型や独自クラスにも対応したくなると、同じような関数がどんどん増えていきます。

ドラゴンボール風に言えば、亀仙流用、神流用、界王流用、惑星戦士流用として、ほとんど同じ修行装置を何台も作っているような状態です。動くには動きますが、管理が大変になります。

関数テンプレートを使う書き方

関数テンプレートを使うと、型だけが違う似た処理を1つにまとめられます。

次のプログラムでは、unite関数をテンプレートとして定義します。これにより、int型にもstring型にも対応できるようになります。

プロジェクト/ファイル名: Chap6_02/main.cpp

#include <iostream>
#include <string>

using namespace std;

// 関数テンプレート
template <typename T>
T unite(T x, T y) {
    return x + y;
}

int main(int argc, char** argv) {
    // int型として利用する
    cout << unite<int>(4, 3) << endl;

    // string型として利用する
    cout << unite<string>(string("かめ"), string("はめ波")) << endl;

    // 引数がどちらもint型なので、型指定を省略できる
    cout << unite(1, 2) << endl;

    // 型が一致しないため、この呼び出しはエラーになる
    // cout << unite(1, 2.3) << endl;

    // プログラムが正常に終わったことを示す
    return 0;
}

実行結果

7
かめはめ波
3

このプログラムでは、unite関数を1つしか定義していません。

template <typename T>
T unite(T x, T y) {
    return x + y;
}

ここで使われているTは、あとから決まる型を表します。

unite(4, 3)と呼び出した場合、Tはintになります。そのため、int型の値を受け取り、int型の結果を返す関数として使われます。

unite(string("かめ"), string("はめ波"))と呼び出した場合、Tはstringになります。そのため、string型の値を受け取り、string型の結果を返す関数として使われます。

つまり、1つのuniteテンプレートが、使う型に合わせてint用にもstring用にもなるわけです。

関数テンプレートの定義

関数テンプレートを定義するときは、関数の前にtemplate を書きます。

template <typename T>

これは、このあとに続く関数がテンプレートであることを示しています。

Tはテンプレート引数と呼ばれます。テンプレート引数は、型を表すための仮の名前です。慣習的にTという名前がよく使われますが、これはTypeの頭文字として覚えると分かりやすいです。

続く関数定義では、戻り値の型、引数の型としてTを使っています。

T unite(T x, T y) {
    return x + y;
}

この関数は、T型のxとT型のyを受け取り、T型の結果を返します。

Tがintなら、次のような関数として扱われます。

int unite(int x, int y) {
    return x + y;
}

Tがstringなら、次のような関数として扱われます。

string unite(string x, string y) {
    return x + y;
}

このように、テンプレートは1つの関数定義から、型に応じた関数を作り出すためのひな型です。

図:テンプレート引数Tが型に置き換わる仕組み

この図が示していること

この図では、関数テンプレートのTが、使う場面に応じてintやstringに置き換わる様子を表しています。

Tは固定された型ではありません。uniteとして使えばintに置き換わり、uniteとして使えばstringに置き換わります。

テンプレートは何でも自動で処理する魔法ではなく、型をあとから当てはめて使うための設計図です。

関数テンプレートの呼び出し方

関数テンプレートを呼び出すときは、関数名のあとに<型名>を付けることで、Tに入る型を明示できます。

整数として使いたい場合は、次のように書きます。

unite<int>(4, 3);

この呼び出しでは、Tがintになります。したがって、4と3をint型として扱い、結果として7が返されます。

文字列として使いたい場合は、次のように書きます。

unite<string>(string("かめ"), string("はめ波"));

この呼び出しでは、Tがstringになります。したがって、2つの文字列が連結され、かめはめ波という文字列が返されます。

呼び出しTに入る型処理内容
unite(4, 3)int整数を合計する
unite(string("かめ"), string("はめ波"))string文字列を連結する
unite(1, 2)int引数からintと判断される

このように、関数名は同じでも、Tに入る型を変えることで、さまざまな型に対応できます。

ドラゴンボール風に言えば、同じ万能修行カプセルでも、整数の修行ポイントを入れれば合計モードになり、技名を入れれば連結モードになるイメージです。

型指定を省略できる場合

関数テンプレートを呼び出すとき、必ず<型名>を書かなければならないわけではありません。

引数から型が明らかな場合、C++はTに入る型を推測できます。

たとえば、次の呼び出しでは、1も2もint型です。

unite(1, 2);

この場合、C++はTをintだと判断できます。そのため、次のように明示しなくても動作します。

unite<int>(1, 2);

もちろん、型をはっきり示したい場合は、unite(1, 2)と書いても問題ありません。

型指定を省略できるかどうかは、引数を見てTを1つの型に決められるかどうかで判断できます。

型が一致しない場合の注意点

今回のテンプレート関数は、次のように定義されています。

T unite(T x, T y)

この形では、第1引数xも第2引数yも同じT型である必要があります。

そのため、次のような呼び出しはエラーになります。

unite(1, 2.3);

1はint型です。2.3はdouble型です。つまり、第1引数と第2引数の型が違います。

しかし、テンプレートではTを1つの型に決めなければなりません。Tをintにすれば第2引数が合わず、Tをdoubleにすれば第1引数が合わないため、コンパイラはどの型を使えばよいか判断できなくなります。

呼び出し使えるか理由
unite(1, 2)使える両方ともint型なのでTをintにできる
unite<int>(1, 2)使えるTをintとして明示している
unite<string>(string("かめ"), string("はめ波"))使えるTをstringとして明示している
unite(1, 2.3)使えないintとdoubleが混在していてTを1つに決められない

この点は、テンプレートを使うときの大切な注意点です。

テンプレートは便利ですが、同じTとして扱う値は、基本的に同じ型でそろえる必要があります。

なぜ同じTに違う型を入れられないのか

Tは、テンプレートの中で1つの型を表します。

今回のunite関数では、戻り値の型もT、第1引数の型もT、第2引数の型もTです。

T unite(T x, T y)

つまり、xとyは同じ型であることが前提です。

Tをintにすると、関数は次のような形になります。

int unite(int x, int y)

Tをdoubleにすると、次のようになります。

double unite(double x, double y)

しかし、unite(1, 2.3)のように、片方がintで片方がdoubleの場合、Tを1つに決めることができません。

このようなとき、C++はあいまいな判断をせず、コンパイルエラーとして知らせてくれます。

これは少し厳しく感じるかもしれませんが、間違った型の組み合わせを早い段階で見つけられるというメリットがあります。

テンプレートを使うメリット

テンプレートを使うと、同じような処理を1つにまとめられます。

テンプレートを使わない場合、int用、string用、double用というように、型ごとに関数を増やしていく必要があります。しかし、テンプレートを使えば、処理の形を1つだけ用意し、型に応じて使い分けられます。

たとえば、unite関数は、2つの値を+でまとめるという考え方を1つのテンプレートにまとめています。これにより、数値の合計にも、文字列の連結にも使えます。

テンプレートを使わない場合テンプレートを使う場合
型ごとに似た関数を作る1つの関数テンプレートでまとめる
関数の数が増えやすいコードを整理しやすい
修正箇所が増えやすい共通部分を管理しやすい
型が増えるたびに関数を追加しやすい汎用的な処理として再利用しやすい

テンプレートを使うことで、コードの重複が減り、処理の意図も見えやすくなります。

ドラゴンボール風に言えば、戦士の種類ごとに修行装置を作るのではなく、どの流派にも対応できる万能修行装置を1つ用意するようなものです。新しい戦士が来ても、同じ仕組みを使って対応できます。

図:テンプレートを使うと1つの仕組みで複数の型に対応できる

この図が示していること

この図では、1つのテンプレート関数が、intにもstringにも対応していることを表しています。

unite(x, y)という1つの仕組みに対して、int型を使えば数値の合計になり、string型を使えば文字列の連結になります。

同じ処理の考え方を、型ごとに何度も書かなくてよいことが分かります。

関数テンプレートのイメージ

関数テンプレートは、型をあとから当てはめる設計図です。

今回のunite関数は、次のようなひな型になっています。

T unite(T x, T y)

このTにintを当てはめると、int型の関数として使えます。

int unite(int x, int y)

このTにstringを当てはめると、string型の関数として使えます。

string unite(string x, string y)

つまり、関数テンプレートは、1つの関数をそのまま実行しているというよりも、型に応じて必要な関数を作るための元になる形だと考えると分かりやすいです。

テンプレートの形Tに入る型実際のイメージ
T unite(T x, T y)intint unite(int x, int y)
T unite(T x, T y)stringstring unite(string x, string y)

このように、テンプレートは型を変えながら同じ処理の形を再利用できます。

テンプレートを使うときの考え方

テンプレートを使うときは、まず「型が違うだけで、処理の流れが同じか」を考えると分かりやすいです。

今回のunite関数では、intでもstringでも、2つの値を受け取り、+でまとめて返すという流れは同じでした。そのため、テンプレートに向いています。

一方で、型によってまったく違う処理をしたい場合は、無理にテンプレートにまとめるとかえって読みにくくなることがあります。テンプレートは便利ですが、何でも1つにまとめればよいというものではありません。

ドラゴンボール風に言えば、修行装置を万能にしすぎると、操作が複雑になりすぎることがあります。共通化できるところはまとめ、流派ごとにまったく違う修行が必要なところは別の処理として作るほうが分かりやすい場合もあります。

このテーマで押さえたいポイント

テンプレートは、型が違うだけで似た処理が増えてしまう場面で役立ちます。

C++では型を厳密に扱うため、int型とstring型を同じように見える処理で扱う場合でも、通常は型ごとに関数を用意する必要があります。しかし、関数テンプレートを使えば、型をあとから指定できるため、同じ処理の形を1つにまとめられます。

template と書くことで、Tを型のひな型として使えるようになります。関数の戻り値や引数にTを使うことで、intにもstringにも対応できる汎用的な関数を作れます。

ただし、Tは1つの型を表します。そのため、T unite(T x, T y)のような定義では、xとyは同じ型である必要があります。unite(1, 2)のように両方がintなら問題ありませんが、unite(1, 2.3)のようにintとdoubleが混在すると、Tを1つに決められないためエラーになります。

テンプレートを使うと、同じ処理を何度も書かずに済みます。コードの重複を減らし、修正しやすく、読みやすいプログラムにできます。

ドラゴンボール風に言えば、テンプレートは型に応じて自動調整される万能修行カプセルです。int型の修行ポイントを入れれば合計し、string型の技名を入れれば連結します。

同じ処理を型ごとに何度も書かず、1つのひな型としてまとめる。
それが、C++のテンプレートを使う大きな目的です。

今回は箇条書きの説明を避けて、文章と表を中心に自然な記事調に整えています。