関数テンプレート 解答ページ | Programming Place Plus 新C++編

トップページ新C++編関数テンプレート

このページの概要 🔗

このページは、練習問題の解答例や解説のページです。



解答・解説 🔗

問題1 (基本★) 🔗

引数に渡した2つの値のうち、大きい方を返す関数テンプレートを作成してください。つまり、標準ライブラリの std::max関数(「UTF-8」のページを参照)と同じものを作ることになります。


たとえば、次のように実装できます。

#include <iostream>

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

int main()
{
    std::cout << max(5, 10) << "\n"
              << max(5, -10) << "\n"
              << max(5, 5) << "\n";
}

実行結果:

10
5
5

使用するときにテンプレート実引数を明示的に指定する必要はなく、(関数に渡すほうの)実引数から自動的に判断されます。これはテンプレートの実引数推論によるものです(本編解説)。

あえて明示的に指定するなら、テンプレート関数名のうしろに <> を使って記入します(本編解説)。

max<int>(5, 10)

問題2 (基本★★) 🔗

任意の要素型の std::vector を渡すと、含まれている要素の平均値を返す関数テンプレートを作成してください。


任意の要素型なので、std::vector<T>T の部分を取り換えられるように関数テンプレートを作成すればいいです。たとえば次のように実装できます。

#include <cassert>
#include <iostream>
#include <vector>

template <typename T>
T average(const std::vector<T>& vec)
{
    assert(!vec.empty());

    T sum {0};
    for (const T& e : vec) {
        sum += e;
    }
    return static_cast<T>(sum / vec.size());
}

int main()
{
    std::vector<int> vec1 {5, 8, 4, 6, 8, 3};
    std::vector<double> vec2 {2.3, 4.1, 1.5, 6.6};

    std::cout << average(vec1) << "\n"
              << average(vec2) << "\n";
}

実行結果:

5
3.625

戻り値の型を要素型と同じ T にしていますが、戻り値の型も指定できるほうがいい可能性もあります。たとえば、要素型は int でも、結果は小数点以下も欲しいので double にしたいかもしれません。その場合は、テンプレート仮引数を追加して対応します。

#include <cassert>
#include <iostream>
#include <vector>

template <typename T, typename Ret = T>
Ret average(const std::vector<T>& vec)
{
    assert(!vec.empty());

    Ret sum {0};
    for (const T& e : vec) {
        sum += static_cast<Ret>(e);
    }
    return static_cast<Ret>(sum / vec.size());
}

int main()
{
    std::vector<int> vec1 {5, 8, 4, 6, 8, 3};
    std::vector<double> vec2 {2.3, 4.1, 1.5, 6.6};

    std::cout << average(vec1) << "\n"
              << average<int, double>(vec1) << "\n"
              << average(vec2) << "\n"
              << average<double, int>(vec2) << "\n";
}

実行結果:

5
5.66667
3.625
3

テンプレート仮引数 T が要素の型、Ret が戻り値の型です。基本的には T と Ret は同じ型でいいと考えて、Ret にはデフォルトテンプレート実引数として T を与えておきます(本編解説)。

Ret がデフォルトで構わないなら、average(vec1) のようなシンプルな記述で呼び出せます。vec1 からテンプレート実引数推論によって T が int であると分かり、Ret はデフォルトテンプレート実引数によって int となります。

戻り値の型を指定したいときは、average<int, double>(vec1) のように、テンプレート実引数を明示的に記述します。

問題3 (応用★★) 🔗

RingBufferクラステンプレートに、要素型が異なる RingBuffer から型変換するテンプレート変換コンストラクタを追加してください。

現状の RingBufferクラステンプレートの実装は、「クラステンプレート」のページにあります。


たとえば次のように実装できます。

// ring_buffer.h
#ifndef RING_BUFFER_H_INCLUDED
#define RING_BUFFER_H_INCLUDED

#include <algorithm>
#include <cassert>
#include <vector>

namespace mylib {

    // リングバッファ
    template <typename T>
    class RingBuffer {

        template <typename T>
        friend class RingBuffer;

    public:
        using container_type = typename std::vector<T>;                     // 内部コンテナの型
        using value_type = typename container_type::value_type;             // 要素型
        using reference = typename container_type::reference;               // 要素の参照型
        using const_reference = typename container_type::const_reference;   // 要素の const参照型
        using pointer = typename container_type::pointer;                   // 要素のポインタ型
        using const_pointer = typename container_type::const_pointer;       // 要素の constポインタ型
        using size_type = typename container_type::size_type;               // サイズ型

    public:
        // コンストラクタ
        //
        // size: 容量
        explicit RingBuffer(size_type capacity);

        // テンプレート変換コンストラクタ
        template <typename U>
        RingBuffer(const RingBuffer<U>& other);


        // 要素を追加
        void push_back(const value_type& value);

        // 要素を取り除く
        void pop_front();

        // 空にする
        void clear();


        // 先頭の要素の参照を返す
        inline reference front()
        {
            assert(empty() == false);
            return m_data[m_front];
        }

        // 先頭の要素の参照を返す
        inline const_reference front() const
        {
            assert(empty() == false);
            return m_data[m_front];
        }

        // 末尾の要素の参照を返す
        inline reference back()
        {
            assert(empty() == false);
            return m_data[get_prev_pos(m_back)];
        }

        // 末尾の要素の参照を返す
        inline const_reference back() const
        {
            assert(empty() == false);
            return m_data[get_prev_pos(m_back)];
        }



        // 容量を返す
        inline size_type capacity() const
        {
            return m_data.size();
        }

        // 要素数を返す
        inline size_type size() const
        {
            return m_size;
        }

        // 空かどうかを返す
        inline bool empty() const
        {
            return m_size == 0;
        }

        // 満杯かどうかを返す
        inline bool full() const
        {
            return m_size == capacity();
        }

    private:
        // 次の位置を返す
        inline size_type get_next_pos(size_type pos) const
        {
            return (pos + 1) % capacity();
        }

        // 手前の位置を返す
        inline size_type get_prev_pos(size_type pos) const
        {
            if (pos >= 1) {
                return pos - 1;
            }
            else {
                return m_size - 1;
            }
        }

    private:
        container_type              m_data;         // 要素
        size_type                   m_size{0};      // 有効な要素の個数
        size_type                   m_back{0};      // 次に push される位置
        size_type                   m_front{0};     // 次に pop される位置
    };


    // コンストラクタ
    template <typename T>
    RingBuffer<T>::RingBuffer(size_type capacity) : m_data(capacity)
    {
    }

    // コンストラクタ(異なる要素型の RingBuffer から作成)
    template <typename T>
    template <typename U>
    RingBuffer<T>::RingBuffer(const RingBuffer<U>& other) :
        m_data(other.m_data.capacity()),
        m_size {other.m_size},
        m_back {other.m_back},
        m_front {other.m_front}
    {
        std::transform(
            std::cbegin(other.m_data),
            std::cend(other.m_data),
            std::begin(m_data),
            [](const auto& e) {
                return static_cast<T>(e);
            }
        );
    }

    // 要素を追加
    template <typename T>
    void RingBuffer<T>::push_back(const value_type& value)
    {
        if (full()) {
            m_front = get_next_pos(m_front);
        }
        else {
            m_size++;
        }

        m_data[m_back] = value;
        m_back = get_next_pos(m_back);
    }

    // 要素を取り除く
    template <typename T>
    void RingBuffer<T>::pop_front()
    {
        assert(empty() == false);

        m_front = get_next_pos(m_front);
        --m_size;
    }

    // 空にする
    template <typename T>
    void RingBuffer<T>::clear()
    {
        m_size = 0;
        m_back = 0;
        m_front = 0;
    }

}

#endif

テンプレート変換コンストラクタの宣言と定義を追加しています。

テンプレート変換コンストラクタなどのメンバ関数テンプレートの定義を、クラステンプレートの定義の外側に書く場合、クラステンプレート側のテンプレート仮引数と、テンプレートコンストラクタ側の仮引数を重ねて記述する必要があります(本編解説)。

テンプレート変換コンストラクタの実装内容は、変換元から変換先にデータメンバ全体をコピーすることですが、m_data だけは std::vector<U> から std::vector<T> へ変換するためのやや複雑な処理が必要です。

まず、変換元の RingBuffer の容量に合わせて、m_data を構築する必要があります(m_data(other.m_data.capacity()))。そして、各要素を、型の変換を行いながら書き写します。for文などを使って1つ1つキャストしながら書き写すことはできますが、ここでは std::transform関数テンプレートを使いました(「要素を探索する」のページを参照)。

また、RingBuffer<T> から RingBuffer<U> の側の private なデータメンバにアクセスする必要があるため、RingBufferクラステンプレートをフレンドクラスにする必要があります(本編解説)。


参考リンク 🔗



更新履歴 🔗




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