関数テンプレート 解答ページ | Programming Place Plus Modern C++編【言語解説】 第11章

トップページModern C++編 C++編](../../index.html) – 第11章

Modern C++編は作りかけで、更新が停止しています。代わりに、C++14 をベースにして、その他の方針についても見直しを行った、新C++編を作成しています。
Modern C++編は削除される予定です。

問題①

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

#include <iostream>
#include <string>

namespace {

    struct MyData {
        int v1;
        int v2;
    };

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

}

int main()
{
    MyData a, b;
    a.v1 = 10;
    a.v2 = 20;
    b.v1 = 20;
    b.v2 = 0;

    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 に対する <<演算子での出力も行えないので、ここでもコンパイルエラーになります。

【上級】演算子オーバーロードという機能を使うと、write_max のテンプレート実引数を MyData型にしてもコンパイルできるようにすることは可能です。

問題②

問題② 次の関数形式マクロを、より安全な方法で置き換えてください。

#define MAX(a,b) ((a) > (b) ? (a) : (b))


インライン関数と関数テンプレートを使います。

#include <iostream>

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

int main()
{
    int a = 10;
    int ans1 = max(a, 5);
    int ans2 = max(++a, 5);

    std::cout << ans1 << "\n"
              << ans2 << std::endl;
}

実行結果:

10
11

関数形式マクロだと、ans2 には 12 という誤った結果が代入されますが、関数テンプレートなら、正しく 11 が代入されます。

問題③

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


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

#include <iostream>

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

namespace {

    template <typename T>
    T* linear_search(T* array, std::size_t size, T value)
    {
        for (std::size_t i = 0; i < size; ++i) {
            if (array[i] == value) {
                return &array[i];
            }
        }

        return nullptr;
    }
    
}

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

    int* p = linear_search(array, SIZE_OF_ARRAY(array), 7);
    if (p == nullptr) {
        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]))

namespace {

    template <typename T>
    const T* linear_search(const T* array, std::size_t size, T value)
    {
        for (std::size_t i = 0; i < size; ++i) {
            if (array[i] == value) {
                return &array[i];
            }
        }

        return nullptr;
    }

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

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

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

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

実行結果:

見つかりませんでした。

const版と非const版とで実装が同じになるので、const版の方に実装を書き、非const版はそれを呼び出すようにします。第10章で取り上げた方法を参考に、static_cast で const を付加することで、const版の方が呼び出されるようにしています。第10章の例と違い、メンバ関数ではないので、引数の方に const を付加しています。


参考リンク


更新履歴

’2017/8/2 新規作成。



第11章のメインページへ

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

Programming Place Plus のトップページへ



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