このページは、練習問題の解答例や解説のページです。
引数に渡した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
使用するときにテンプレート実引数を明示的に指定する必要はなく、(関数に渡すほうの)実引数から自動的に判断されます。これはテンプレートの実引数推論によるものです(本編解説)。
あえて明示的に指定するなら、テンプレート関数名のうしろに
<>
を使って記入します(本編解説)。
<int>(5, 10) max
任意の要素型の std::vector を渡すと、含まれている要素の平均値を返す関数テンプレートを作成してください。
任意の要素型なので、std::vector<T>
の
T
の部分を取り換えられるように関数テンプレートを作成すればいいです。たとえば次のように実装できます。
#include <cassert>
#include <iostream>
#include <vector>
template <typename T>
(const std::vector<T>& vec)
T average{
assert(!vec.empty());
{0};
T sum for (const T& e : vec) {
+= e;
sum }
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>
(const std::vector<T>& vec)
Ret average{
assert(!vec.empty());
{0};
Ret sum for (const T& e : vec) {
+= static_cast<Ret>(e);
sum }
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)
のように、テンプレート実引数を明示的に記述します。
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>
(const RingBuffer<U>& other);
RingBuffer
// 要素を追加
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>
<T>::RingBuffer(size_type capacity) : m_data(capacity)
RingBuffer{
}
// コンストラクタ(異なる要素型の RingBuffer から作成)
template <typename T>
template <typename U>
<T>::RingBuffer(const RingBuffer<U>& other) :
RingBufferm_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で書く |
RSS | 管理者情報 | プライバシーポリシー |