C API の range ラッパー
以前少し考えたことがあったのですが、最近同じ話題を見かけたのでまとめてみました。
Range の要素がメモリの連続領域にあること (すなわち、&*(begin(r)+n)==&*begin(r)+n が常に成り立つこと) が保証されていれば、その range のデータをレガシーな C API とのやりとりに使うことができます。
Effective STL 第16項の例
void doSomething(const int* pInt, size_t numInts); vector<int> v; if (!v.empty()) { doSomething(&v[0], v.size()); }
このような range を、標準と Boost からざっと挙げてみます。
- 配列
- std::array
- std::basic_string (C++0x)
- std::vector (except std::vector
) - boost::array
- boost::multi_array
- boost::container::basic_string
- boost::container::vector
数としてはそんなになさそうです。そこで、これらが渡されたときだけ true を返すようなメタ関数 has_contiguous_storage を、特殊化を使って書いてみます*1。
https://gist.github.com/912263
他に条件を満たす range があっても、特殊化を追加することで対応できます。そして、一度このようなメタ関数を書いておけば、C API の range ラッパーを書くことが容易になります。
void doSomething(const int* pInts, size_t numInts); template <class Range> void do_something(Range const &r, typename std::enable_if<has_contiguous_storage<Range>::value>::type * = 0) { if (!boost::empty(r)) doSomething(&*boost::begin(r), boost::size(r)); } template <class Range> void do_something(Range const &r, typename std::enable_if<!has_contiguous_storage<Range>::value>::type * = 0) { std::vector<int> const v(boost::begin(r), boost::end(r)); doSomething(v.data(), v.size()); }
データが連続に格納されている場合は &*begin(r) と size(r) をそのまま渡し、そうでない場合は boost::container::vector std::vector にデータを移してから API に渡します (std::vector を使わないのは bool 避けです)。
ここまで一般化する必要はないかもしれませんが、C API とやりとりする方法を知っておくことは良いことだと思います。