はじめに
テンプレート引数T
を具体化せずに、std::vector<T>::iterator
を引数に持つ関数をつくりたい。
具体的にはこんな関数を実装したい。
template<typename T>
void f(typename std::vector<T>::iterator begin, typename std::vector<T>::iterator end)
{
//...
}
しかし、これはコンパイルエラーになる。
色々と検索したら一般化した話は出てくるんだけど、C++難しい…ってなる話ばかりなので、上の目的に特化して解決策を書いてみる。
背景
外部ライブラリに、void f(float* p, int n)
のように配列を生ポインタと長さを表す整数で受ける関数があり、iterator
入れられたら便利だなぁってことでラッパー関数を実装しようとした時のこと。
STLのコンテナクラスには、ポインタで要素に連続アクセスできるもの(例: std::vector<T>
)とできないもの(例: std::deque<T>
)があるので、前者はそのままポインタでぶちこんで、後者はvector等にコピーした後ぶちこむ、というように処理を分けたかった。
前者はstd::array<T, N>
かstd::vector<T>
ぐらいなので、とりあえず使用頻度の高いstd::vector<T>
のみを考える。
めんどくさがりなので、std::vector<T>
のテンプレート引数T
を抽象化したまま扱いたかったんだけど、上記のような素直な書き方はコンパイルできませんでしたってお話。
実装
コンパイルに失敗する原因
そもそもコンパイルに失敗する原因を明確にしておこう。
Wandboxでclang13.0.0を指定して下のコードを実行してエラーを確認する。
#include <iostream>
#include <algorithm>
#include <vector>
template<typename T>
void f(typename std::vector<T>::iterator begin, typename std::vector<T>::iterator end)
{
// ただvectorの中身を表示するだけ
std::for_each(begin, end, [](const auto& x) { std::cout << x << " "; });
std::cout << std::endl;
}
int main()
{
std::vector<float> v = {1.f, 2.f, 3.f};
f(v.begin(), v.end());
return 0;
}
prog.cc:16:5: error: no matching function for call to 'f'
f(v.begin(), v.end());
^
prog.cc:6:6: note: candidate template ignored: couldn't infer template argument 'T'
void f(typename std::vector<T>::iterator begin, typename std::vector<T>::iterator end)
^
1 error generated.
テンプレート引数T
を推測できないらしい。
調べると::の左側にあるテンプレート引数は推測できないことがわかった1。言語仕様なので、仕方がない。
テンプレート引数T
のところにfloat
等の具体的な型を入れてオーバーロードする手もあるが、同じ処理するならなるべくまとめたい。
解決策1
std::vector<T>
のテンプレート引数T
が推測できないなら、推測できる形に変形すればいい。
具体的には、以下のようにiterator
自体をテンプレート引数とした関数で受け取り、そこからstd::vector<T>
のテンプレート引数T
を推測してから、元の関数に入れてやる。
#include <iostream>
#include <algorithm>
#include <vector>
template<typename T>
void f(typename std::vector<T>::iterator begin, typename std::vector<T>::iterator end)
{
// ただvectorの中身を表示するだけ
std::cout << "vector specialized" << std::endl;
std::for_each(begin, end, [](const auto& x) { std::cout << x << " "; });
std::cout << std::endl;
}
template<typename Iterator, typename T = typename Iterator::value_type>
void g(Iterator begin, Iterator end)
{
f<T>(begin, end);
}
int main()
{
std::vector<float> v = {1.f, 2.f, 3.f};
g(v.begin(), v.end());
return 0;
}
これは無事にコンパイルできるし、正常に動作する。
これで、テンプレート引数Tを具体化せずに、std::vector::iteratorを引数に持つ関数をつくることができました。
めでたしめでたし。
…と言いたいところだが、当初の目的はSTLのコンテナクラスのうち、ポインタで要素に連続アクセスできるもの(例: std::vector<T>
)とできないもの(例: std::deque<T>
)のそれぞれに対して、iterator
を受け取る関数を別々に定義したい、というところだった。
上記の解決策1では、value_type
を持つ型であれば全て関数g
を通るため、テンプレート引数Iterator
がstd::vector<T>::iterator
なのか、std::deque<T>::iterator
なのかは関数f
で分けることになる。
std::vector<T>
のiteratorに対してのみ適用される形で関数f
を書いているが、実際にはstd::deque<T>
やstd::set<T>
に対してもそれぞれ関数f
をオーバーロードして書かなければならず、めんどくさい。
std::vector<T>
以外のiterator
を受け取る関数をテンプレートでまとめようとすると、今度は関数g
の中身もめんどくさいことになる。
解決策2
結局、要素型T
を受け取りつつ、任意のiterator
をテンプレートで受け取れるような形をつくってあげる。
具体的には、先ほどの関数g
をクラステンプレートの静的メンバー関数にしてあげればよい。
#include <iostream>
#include <algorithm>
#include <vector>
#include <deque>
template<typename T>
void print(const T* p, int n)
{
// ただ中身を表示するだけ
std::for_each(p, p + n, [](const auto& x) { std::cout << x << " "; });
std::cout << std::endl;
}
template<typename T>
class Foo
{
public:
// vector専用
// 実際には std::vector<T>::const_iterator も考慮する必要がある
static void f(typename std::vector<T>::iterator begin, typename std::vector<T>::iterator end)
{
std::cout << "vector specialized" << std::endl;
print(&(*begin), static_cast<int>(end - begin)); // vectorは生ポによる要素の連続アクセス可能
}
// 抽象化したやつ
template<typename Iterator>
static void f(Iterator begin, Iterator end)
{
std::cout << "generalized" << std::endl;
std::vector<T> tmp(begin, end); // vectorにコピーして生ポ扱えるようにしてからぶちこむ
print(tmp.data(), static_cast<int>(tmp.size()));
}
};
template<typename Iterator, typename T = typename Iterator::value_type>
void g(Iterator begin, Iterator end)
{
Foo<T>::f(begin, end);
}
int main()
{
std::vector<float> v = {1.f, 2.f, 3.f};
std::deque<float> d = {1.f, 2.f, 3.f};
std::cout << "=====vector=====" << std::endl;
g(v.begin(), v.end());
std::cout << "=====deque=====" << std::endl;
g(d.begin(), d.end());
return 0;
}
これを実行すると、以下のように、std::vector<T>
に対しては専用のもの、std::deque<T>
に対してはテンプレート引数Iterator
で抽象化したものが呼び出されていることがわかる。
=====vector=====
vector specialized
1 2 3
=====deque=====
generalized
1 2 3
これで、std::vector<T>
と、ポインタで要素に連続アクセスできないもの(例: std::deque<T>
)のそれぞれに対し、iterator
を受け取る関数を定義できました。めでたしめでたし。
おわりに
こんな形で実装しましたが、もっと簡単にできるよ!って方法あったら教えてください。
C++難しい… 1ミリもわからん…
余談
今回、STLのコンテナクラスのうち、ポインタで要素に連続アクセスできるものとしてstd::vector<T>
のみを扱ったが、実際には固定長配列のstd::array<T, N>
もある。
これも同様にstd::array<T, N>::iterator
を引数に持つ関数を作ろうとするんだけど、配列長を表すテンプレート引数N
はiterator
から取得できない…
そもそもstd::array<T, N>
のiterator
は完全に実装依存なので、例えばmsvcでは_Array_Iterator<T, N>
というクラスなんだけど、gccではT*
のように生ポインタで定義されているので、後者の場合はどうやってもテンプレート引数N
は推測できない…
SFINAEでごにょごにょすれば出来るかもしれんけど、進んで沼には入りたくないよねぇ…