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 とやりとりする方法を知っておくことは良いことだと思います。

*1:これらの range の部分列を表す boost::iterator_range や boost::sub_range も含めるべきですが、簡単のため考慮していません