#include <string>
#include <vector>
#include <set>
#include <list>
template<class T> void put(T cursor,T end);
int main(){
std::vector<std::string> v;
v.push_back( "a" );
v.push_back( "b" );
v.push_back( "c" );
put( v.begin() , v.end() );
std::set<std::string> s;
s.insert( "1" );
s.insert( "2" );
s.insert( "3" );
put( s.begin() , s.end() );
}
#include <iostream>
template<class T> void put(T cursor,T end)
{
for( ; cursor != end ; cursor++ ){
std::cout << *cursor << std::endl;
}
}
これをビルドするとリンクエラーになるので、関数 put をヘッダファイルに置くか、sub0.cpp 側ですべての想定される T の型に対して実体化しておかなくていけない1
put が巨大な関数の場合、前者はビルドが遅くなるし、後者のようなものはきちんとメンテしないと使わない無駄な実体が発生してしまう。
うまいことソースを分割したまま、任意のコンテナの要素を列挙できる仕組みは作れないか
- std::vector / std::set 両方からデータを取れる関数をあまりテンプレートにしたくない - 標準愚痴出力
- (続) std::vector / std::set 両方からデータを取れる関数をあまりテンプレートにしたくない - 標準愚痴出力
- (続々) std::vector / std::set 両方からデータを取れる関数をあまりテンプレートにしたくない - 標準愚痴出力
関数側 (sub.cpp)
#include <string>
#include <iostream>
#include "enumerate.h"
void put(enumerator<std::string> &each)
{
std::string value;
while( each(value) ){
std::cout << value << std::endl;
}
}
sub.cpp
側では、<vector>
も<set>
も include していない ( ただし、"enumerate.h"
の中で<functional.h>
だけは include している。詳しくは後述 )std::vector
やstd::set
などのコレクションを引き受ける引数の型はenumerator<T>
という型にしておく.
呼び出し側 (main.cpp)
#include <string>
#include <vector>
#include <set>
#include <list>
#include "enumerate.h"
extern void put(enumerator<std::string> &each);
int main(void)
{
std::vector<std::string> v;
v.push_back( "a" );
v.push_back( "b" );
v.push_back( "c" );
put( make_enumerator(v) );
std::set<std::string> s;
s.insert( "1" );
s.insert( "2" );
s.insert( "3" );
put( make_enumerator(s) );
std::list<std::string> ls;
ls.push_back( "x" );
ls.push_back( "y" );
ls.push_back( "z" );
put( make_enumerator(ls) );
}
- 呼び出す際は、
std::vector<T>
やstd::set<T>
をmake_enumerator
という関数で、enumerator<T>
へ変換して、put
へ引き渡す.
a
b
c
1
2
3
x
y
z
実装 (enumerate.h)
-
make_enumerator<T>
という型はbool operator()(bool(T&))
というメソッドを持っており、関数の体裁で呼び出すことができる -
つまり、関数オブジェクトであるから、
std::function<bool(T&)>
へ変換することができる。 -
ゆえに関数側のパラメータは
std::set
やstd::vector
などの型に固定しなくてよくなる。 -
この二つの組み合わせだけでも利用可能であるが、見かけの体裁を読みやすくするため、次のようなガワをかぶせた。
function<bool(T&)>
の別名としてenumerator<T>
を定義(using)
#include <functional>
template <typename T>
class make_enumerator {
typename T::iterator m_cursor,m_end;
public:
make_enumerator(T &t)
: m_cursor(t.begin()) , m_end(t.end()) {}
bool operator() (typename T::value_type &store) {
if( m_cursor == m_end ){
return false;
}
store = *m_cursor;
m_cursor++;
return true;
};
};
template <typename T>
using enumerator = const std::function<bool(T&)>;
#include <string>
#include <iostream>
#include "enumerate2.h"
void sub(pair_enumerator<std::string,int> &each)
{
std::string key;
int value;
while( each(key,value) ){
std::cout << key << ":" << value << std::endl;
}
}
#include <string>
#include <map>
#include <unordered_map>
#include "enumerate2.h"
extern void sub(pair_enumerator<std::string,int> &each);
int main(){
std::map<std::string,int> map;
map["ahaha"] = 1;
map["ihihi"] = 2;
map["ufufu"] = 3;
sub(make_pair_enumerator(map));
std::unordered_map<std::string,int> umap;
umap["AHAHA"] = 1;
umap["IHIHI"] = 2;
umap["UFUFU"] = 3;
sub(make_pair_enumerator(umap));
}
#include <functional>
template <typename T>
class make_pair_enumerator {
typename T::iterator m_cursor,m_end;
public:
make_pair_enumerator(T &t)
: m_cursor(t.begin()) , m_end(t.end()) {}
bool operator() (typename T::key_type &first,
typename T::mapped_type &second) {
if( m_cursor == m_end ){
return false;
}
first = m_cursor->first;
second = m_cursor->second;
m_cursor++;
return true;
};
};
template <typename Key,typename Mapped>
using pair_enumerator = const std::function<bool(Key&,Mapped&)>;
Footnotes
-
externtemplate というものを使う。例:main0_.cpp, sub0_.cpp ; 参考文献:[C++11] extern templateの機能とその使い道 - Qiita ↩