C++編【言語解説】 第9章 関数テンプレート 解答ページ

先頭へ戻る

問題①

問題① 次のプログラムがコンパイルエラーになる理由を答えてください。

#include <iostream>
#include <string>

struct MyData {
    int value;
    std::string name;
};

template <typename T1, typename T2>
void write_max(T1 a, T2 b)
{
    std::cout << ((a >= b) ? (a) : (b)) << std::endl;
}

int main()
{
    MyData a, b;
    a.value = 10;
    a.name = "aaa";
    b.value = 20;
    b.name = "bbb";

    write_max(a, b);
}

関数テンプレートwrite_max を呼び出す際に、実引数として MyData型の変数を渡しています。明示的にテンプレート実引数を指定していないので、テンプレート実引数の推定が行われ、テンプレート仮引数 T1、T2 はそれぞれ、MyData型になります。したがって、write_max は次のように実体化されます。

void write_max(MyData a, MyData b)
{
    std::cout << ((a >= b) ? (a) : (b)) << std::endl;
}

そうすると問題なのは、以下の部分です。

(a >= b)

構造体型同士で、>=演算子による比較は行えないため、コンパイルエラーが起きます。また、std::cout に対する <<演算子での出力も行えないので、ここでもコンパイルエラーになります。

演算子オーバーロード(第19章および第35章)という機能を使うと、write_max のテンプレート実引数を MyData型にしてもコンパイルできるようにすることは可能です。

問題②

問題② 任意の型の配列から、任意の値を線形探索(アルゴリズムとデータ構造編【探索】第1章)で探す関数テンプレートを作成してください。


有用なアルゴリズムが特定の型に依存してしまうのはもったいないですし、単純に言って不便です。こういう用途でも、関数テンプレートは非常に便利な機能です。

#include <iostream>

#define SIZE_OF_ARRAY(array)	(sizeof(array)/sizeof(array[0]))

template <typename T>
T* linear_search(T* array, size_t size, T value)
{
    size_t i;

    for (i = 0; i < size; ++i) {
        if (array[i] == value) {
            return &array[i];
        }
    }

    return NULL;
}

int main()
{
    int array[] = { 5, 12, 7, -8, -3, 9 };
    int* p;

    p = linear_search(array, SIZE_OF_ARRAY(array), 7);
    if (p == NULL) {
        std::cout << "見つかりませんでした。" << std::endl;
    }
    else {
        std::cout << "見つかりました。" << std::endl;
    }
}

実行結果:

見つかりました。

linear_search関数テンプレートの第1引数に const が付いていませんが、関数内で書き換えを行っていないという事実から言って、const を付ける方が自然ではあります。ただその場合、戻り値の方も const を付けなくてはならないので、関数の呼び出し側で、返されてきたポインタが指す先の値を書き換えることができなくなります。

そこで、C++ には関数オーバーロードがあることを思い出して、const版と非const版を作るという手があります。

#include <iostream>

#define SIZE_OF_ARRAY(array)	(sizeof(array)/sizeof(array[0]))

template <typename T>
const T* linear_search(const T* array, size_t size, T value)
{
    size_t i;

    for (i = 0; i < size; ++i) {
        if (array[i] == value) {
            return &array[i];
        }
    }

    return NULL;
}

template <typename T>
T* linear_search(T* array, size_t size, T value)
{
    size_t i;

    for (i = 0; i < size; ++i) {
        if (array[i] == value) {
            return &array[i];
        }
    }

    return NULL;
}

int main()
{
    int array[] = { 5, 12, 7, -8, -3, 9 };
    const int* cp;
    int* p;

    p = linear_search(array, SIZE_OF_ARRAY(array), 7);
    if (p != NULL) {
        *p = 0;  // 見付かったら、0 に書き換える
    }

    cp = linear_search(array, SIZE_OF_ARRAY(array), 7);
    if (cp == NULL) {
        std::cout << "見つかりませんでした。" << std::endl;
    }
    else {
        std::cout << "見つかりました。" << std::endl;
    }
}

実行結果:

見つかりませんでした。

このように、const版と非const版での関数オーバーロードが便利なケースはよくあります。ただ、2つの linear_search関数がしていることはまったく同じなので、2か所に同じコードが登場してしまっています。これは保守上、良いことではありません。

そこで両者を1つにまとめてみます。

template <typename T>
const T* linear_search(const T* array, size_t size, T value)
{
    size_t i;

    for (i = 0; i < size; ++i) {
        if (array[i] == value) {
            return &array[i];
        }
    }

    return NULL;
}

template <typename T>
T* linear_search(T* array, size_t size, T value)
{
    return const_cast<T*>(linear_search(static_cast<const T*>(array), size, value));
}

非const版の方から、const版の関数を呼び出すようにして、処理を任せてしまうのが正しい実装方法です。そのために、static_cast を使って const を付加しています。返された結果には const が付いているので、最後に const_cast で const を外します。

const版から非const版を呼び出す形でもいいように思えるかもしれませんが、それだと const の安全性が失われます。非const版の方に処理の実装を書くと、誤って書き換えを行うコードを書いても、コンパイラが検出してくれません。


参考リンク



更新履歴

'2018/8/21 練習問題②を第10章へ移動。③を②に変更。

'2015/12/27 SIZE_OF_ARRAYマクロの定義を修正。

'2014/3/1 新規作成。



第9章のメインページへ

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

Programming Place Plus のトップページへ


はてなブックマーク Pocket に保存 Twitter でツイート Twitter をフォロー
Facebook でシェア Google+ で共有 LINE で送る rss1.0 取得ボタン RSS
管理者情報 プライバシーポリシー