C++編【言語解説】 第33章 メンバ関数テンプレート

この章の概要

この章の概要です。

メンバ関数テンプレート

関数がテンプレート化できるのと同様に、メンバ関数をテンプレート化することができます。 この場合、メンバ関数テンプレート(メンバテンプレート)と呼ばれます。

なお、メンバ関数テンプレートは仮想関数(第27章)にすることができません。

メンバ関数テンプレートは、所属するクラス自体はテンプレートでない場合と、 クラス自体がクラステンプレートになっている場合とがあります。

通常のクラスに、テンプレート化されたメンバ関数がある

まず、所属するクラス自体はテンプレートでない場合の形を見ていきましょう。

#include <iostream>

class DataStore {
public:
	explicit DataStore(double value) :
		mValue(value)
	{}

	template <typename U>
	inline const U GetValue() const
	{
		return static_cast<U>(mValue);
	}

private:
	double    mValue;
};


int main()
{
	DataStore ds(10.5);

	std::cout << ds.GetValue<int>() << "\n"
	          << ds.GetValue<double>() << std::endl;
}

実行結果:

10
10.5

DataStoreクラスの GetValue関数テンプレートは、テンプレート引数に指定した型へ static_cast で変換を行い、値を返しています。 メンバ関数テンプレートの使い道として一番多いのは、この例のように、型変換を行うことです。

なお、メンバ関数テンプレートの実装をクラス外に書く場合は、次のようになります。

template <typename U>
const U DataStore::GetValue() const
{
	return static_cast<U>(mValue);
}

「template <typename U>」の部分が必要なのは、クラステンプレートのメンバ関数のときと同様ですが、 DataStore は通常のクラスなので、「DataStore<T>::」ではなく「DataStore::」になります。

ところで、先ほどの例の場合、「ds.GetValue()」のように型指定の無い使い方ができません。 型変換を目的としておらず、デフォルトの型(この場合は double型)のまま返してほしい場合には、 明示的な型指定無しで使える方が便利でしょう。 そこで、テンプレートでない通常のメンバ関数としての GetValue() を追加します。

#include <iostream>

class DataStore {
public:
	explicit DataStore(double value) :
		mValue(value)
	{}

	inline double GetValue() const
	{
		return mValue;
	}

	template <typename U>
	inline const U GetValue() const
	{
		return static_cast<U>(GetValue());
	}

private:
	double    mValue;
};


int main()
{
	DataStore ds(10.5);

	std::cout << ds.GetValue<int>() << "\n"
	          << ds.GetValue() << std::endl;
}

実行結果:

10
10.5

テンプレート版の GetValue関数は、非テンプレート版の GetValue関数を呼び出す形に変更しています。 これは必須ではありませんが、目的から言って、呼出し元へ返す型を変換すること以外は同じにしたいはずなので、 保守の面から、処理を移譲させるようにしました。
無限再帰してしまいそうに見えますが、非テンプレート版はテンプレート版よりも優先度が高いので問題ありません

C++11 では、メンバ関数テンプレートのテンプレートパラメータに、デフォルト引数を与えられるので、 これを利用する手もあります。

クラステンプレートが、テンプレート化されたメンバ関数を持っている

次に、クラステンプレート内でメンバ関数テンプレートを使う形を見てみましょう。 先ほどの DataStoreクラスをクラステンプレートに変えたものを例に挙げます。

#include <iostream>

template <typename T>
class DataStore {
public:
	explicit DataStore(const T& value) :
		mValue(value)
	{}

	inline const T GetValue() const
	{
		return mValue;
	}

	template <typename U>
	inline const U GetValue() const
	{
		return static_cast<U>(GetValue());
	}

private:
	T    mValue;
};


int main()
{
	DataStore<double> ds(10.5);

	std::cout << ds.GetValue<int>() << "\n"
	          << ds.GetValue() << std::endl;
}

実行結果:

10
10.5

クラステンプレートになっても、メンバ関数テンプレートの宣言に変化はありません。 ただし、実装をクラステンプレートの定義の外側に書く場合は、クラステンプレートのテンプレートパラメータと、 メンバ関数テンプレートのテンプレートパラメータの両方を記述しなければならないため、次のように少々面倒な形になります。

template <typename T>
template <typename U>
const U DataStore<T>::GetValue() const
{
	return static_cast<U>(GetValue());
}

C++11 (デフォルトテンプレート引数)

C++11

C++11 では、メンバ関数テンプレートのテンプレートパラメータに、デフォルトの引数を与えることが可能になりました。

#include <iostream>

class DataStore {
public:
	explicit DataStore(double value) :
		mValue(value)
	{}

	template <typename U = double>
	inline const U GetValue() const
	{
		return static_cast<U>(mValue);
	}

private:
	double    mValue;
};


int main()
{
	DataStore ds(10.5);

	std::cout << ds.GetValue<int>() << "\n"
	          << ds.GetValue() << std::endl;
}

実行結果:

10
10.5

この機能は、VisualC++ 2013/2015/2017、clang 3.7 のいずれでも使用できます。

コンストラクタテンプレート

コンストラクタもテンプレート化することができます。 これは、コンストラクタテンプレートと呼ばれます。

一般に、コンストラクタテンプレートの意義は、クラステンプレートに異なるテンプレート引数を与えてインスタンス化されたオブジェクトがあるとき、 互いの暗黙的な変換を受け付けることにあります。

例えば、DataStoreクラステンプレートが、次のようにコピーコンストラクタを定義していたとします。

template <typename T>
class DataStore {
public:
	DataStore(const DataStore<T>& rhs);
};

コピーコンストラクタがあれば、DataStore<int>型のオブジェクトを使って、DataStore<int>型の新たなオブジェクトを作り出せます。

DataStore<int> iStore(10);
DataStore<int> iStore2(iStore);  // OK

ところが、次のコードはコンパイルエラーになります。

DataStore<int> iStore(10);
DataStore<double> dStore(iStore);  // エラー

これは、DataStore<int>型のオブジェクトを使って、DataStore<double>型の新たなオブジェクトを作ろうとしている訳ですが、 DataStoreクラステンプレートのテンプレートパラメータ T に当てはめられる型が異なるため、 定義したコピーコンストラクタが使えずに、コンパイルエラーになります。

このような場面で、テンプレートパラメータの違いがあっても、同一の型であるように扱いたい場合、 コンストラクタテンプレートを利用して解決を計ることができます。

#include <iostream>

template <typename T>
class DataStore {
	template <typename>
	friend class DataStore;

public:
	explicit DataStore(const T& value) :
		mValue(value)
	{}

	// DataStore<U> からコピー
	template <typename U>
	DataStore(const DataStore<U>& rhs) :
		mValue(static_cast<T>(rhs.mValue))
	{}

	inline const T GetValue() const
	{
		return mValue;
	}

private:
	T    mValue;
};

int main()
{
	DataStore<int> iStore(10);
	DataStore<double> dStore(iStore);  // OK

	std::cout << iStore.GetValue() << "\n"
	          << dStore.GetValue() << std::endl;
}

実行結果:

10
10.0

コピーコンストラクタ自体がテンプレート化された訳ですが、 形としては、メンバ関数テンプレートそのものであることが分かると思います。 結果、DataStore<T> のオブジェクトを作るために DataStore<U> を使えるようになりました。

メンバ変数 mValue をコピーする際には、U型から T型への型変換が必要になります。 ここで static_cast をしていますが、U型と T型の関係性が static_cast でキャストできないようなものであれば、 この部分でコンパイルエラーが起きます。
また、このとき、DataStore<int> と DataStore<double> は別の型なので、 「rhs.mValue」という相手方の「非公開」なメンバをアクセスしなくてはなりません。 そのため、相手をフレンドクラス(第25章)として指定しておく必要があります

テンプレート変換演算子

第19章で説明した変換演算子についても、テンプレートにすることができます。 テンプレート変換演算子により、任意の型への型変換が定義できます。

#include <iostream>

class DataStore {
public:
	explicit DataStore(double value) :
		mValue(value)
	{}

	template <typename T>
	inline operator T() const
	{
		return static_cast<T>(mValue);
	}

private:
	double    mValue;
};

int main()
{
	DataStore fStore(123.456);

	int i = fStore;
	double f = fStore;
	std::cout << i << "\n"
	          << f << std::endl;
}

実行結果:

123
123.456

テンプレート変換演算子は、代入や初期化の際の左辺側の型によって、テンプレートパラメータを推測していると考えられます。

テンプレート変換演算子と、テンプレートでない変換演算子を共存させることは可能です。 その場合、いつものように、より一致度が高い変換演算子が優先的に使用されるので、例えば上記の例で言えば、 int型に変換する場合にだけ、テンプレートでない変換演算子を使わせるといったこともできます。


練習問題

問題@ 次のようなクラスがあります。

template <typename T>
class DataStoreArray {
public:
	void Push(const T& data)
	{
		mVec.push_back(data);
	}

private:
	std::vector<T> mVec;
};

このクラスに、保持している要素を、STLコンテナにコピーして返すようなテンプレート変換演算子を追加して下さい (map はキーと値のペアが必要になるので省いて良いです)。 つまり、以下の変換を可能にして下さい。

DataStoreArray<int> store;

std::vector<int> v = store;
std::list<int> l = store;
std::set<int> s = store;


解答ページはこちら

参考リンク

更新履歴

'2017/3/25 VisualC++ 2017 に対応。

'2016/11/5 新規作成。



前の章へ

次の章へ

C++編のトップページへ

Programming Place Plus のトップページへ