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

トップページC++編

C++編で扱っている C++ は 2003年に登場した C++03 という、とても古いバージョンのものです。C++ はその後、C++11 -> C++14 -> C++17 -> C++20 -> C++23 と更新されています。
なかでも C++11 での更新は非常に大きなものであり、これから C++ の学習を始めるのなら、C++11 よりも古いバージョンを対象にするべきではありません。特に事情がないなら、新しい C++ を学んでください。 当サイトでは、C++14 をベースにした新C++編を作成中です。

この章の概要

この章の概要です。


メンバ関数テンプレート

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

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

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

次に、クラステンプレート内でメンバ関数テンプレートを使う形を見てみましょう。先ほどの 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


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

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

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

たとえば、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;


解答ページはこちら

参考リンク


更新履歴

’2018/1/5 コンパイラの対応状況について、対応している場合は明記しない方針にした。

’2017/7/30 clang 3.7 (Xcode 7.3) を、Xcode 8.3.3 に置き換え。

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

’2016/11/5 新規作成。



前の章へ (第32章 例外)

次の章へ (第34章 関数オブジェクト)

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

Programming Place Plus のトップページへ



はてなブックマーク に保存 Pocket に保存 Facebook でシェア
X で ポストフォロー LINE で送る noteで書く
rss1.0 取得ボタン RSS 管理者情報 プライバシーポリシー
先頭へ戻る