コンテナと for ループ

次のようなコード断片、 iterator を明示的に使う for ループが見苦しく感じる。

void hexstr(
  const std::vector<unsigned char>& bytes,
  std::vector<char>& out)
{
  for(std::vector<unsigned char>::const_iterator iter = bytes.begin();
    iter != bytes.end();
    ++iter)
  {
    int byte = (int)*iter;
    const char hexchars[] = "0123456789ABCDEF";
    out.push_back(hexchars[(byte>>4)&15]);
    out.push_back(hexchars[byte&15]);
  }
}

STL アルゴリズムを使えば、 iterator を陽に使わずに済ませられる。

#include <iterator>
#include <numeric>
#include <vector>

typedef std::vector<unsigned char> ucs_t;
typedef std::vector<char> cs_t;

namespace {
  template<typename T>
  inline T aux(T it, unsigned char v) {
    static const char alphabet[] = "0123456789ABCDEF";
    *it++ = alphabet[v / 16];
    *it++ = alphabet[v % 16];
    return it;
  }
}

void hexstr(const ucs_t& ucs, cs_t &cs) {
  std::accumulate(ucs.begin(), ucs.end()
    , std::back_inserter(cs), aux<std::back_insert_iterator<cs_t> >);
}

残念なところは、 for ループならそばに定義できる処理を、別の関数として実体定義しなければならないこと。

C++0x の lambda で積算

C++0x の lambda を使えば回避できる…気がするけれど、手元の g++ 4.2 はまだ lambda をサポートしていないため試せない。入れてみるか…?
C++0x の lambda を使うと、この不便は回避できる。

void hexstr(const ucs_t& ucs, cs_t &cs) {
  std::accumulate(ucs.begin(), ucs.end(), std::back_inserter(cs)
                  , [](std::back_insert_iterator<cs_t> it, unsigned char v) {
                    static const char alphabet[] = "0123456789ABCDEF";
                    *it++ = alphabet[v / 16];
                    *it++ = alphabet[v % 16];
                    return it;
                  } );

g++4.5 で lambda を有効にするには -std=c++0x をオプション引数で渡すこと。
もう一点、この例の lambda 式のひとつめの引数の型について。 STL を使っていて悩むことのひとつに、値をつくりだすユーティリティを使って構築した値の型の特定が難しいことがある。 std::back_inserter(cs) の返す値の型がなんになるか、ライブラリーのドキュメントを調べるにせよ、あるいはコンパイルしてみてエラーから特定するにせよ、いずれにせよ面倒である。これについて C++0x で導入される decltype を使ってコンパイラーに型の判断をまかせることが可能である。
ここでは std::back_insert_iterator it の代わりに decltype(std::back_inserter(cs)) it と書ける。

外部環境をキャプチャーして積算

先の accumulate は、結果を収集するコレクティングパラメーターを明示するために accumulate アルゴリズムを使用したけれど、外部環境を手軽にキャプチャーできる lambda の特性を活かしてみてもよい。
次のようにすれば、もともとの for ループでの実装に近くなる。

void hexstr(const ucs_t& ucs, cs_t &cs) {
  std::for_each(ucs.begin(), ucs.end(), [&cs](unsigned char v) {
      static const char alphabet[] = "0123456789ABCDEF";
      cs.push_back(alphabet[v / 16]);
      cs.push_back(alphabet[v % 16]);
    });
}