可変長引数の関数を書こうとした話

Motivation

動機は構造体をバイナリ形式でファイルに書き出したい、ただそれだけの他愛もない話です。 何も考えずに書くと以下のようになります。

struct A {
       uint8_t b;
       uint32_t c;
};

A a = {'a', 3};
fwrite(&a, sizeof(a), 1, fp);

このコードを実行するとfpの指すファイルに8バイト(Mac OS Xの場合)書き出すことになります。 uint8_t型1つとuint32_t型1つなので、5バイトじゃないかという話があるかと思いますが、構造体は効率を考えてバイト境界にアライメントされるため、メンバbとメンバcの間に3バイトのパディングが入っています。

もちろん、できるだけパディングが入らないように注意して構造体を設計しましょうという話もあるかと思いますが、結局のところ環境依存であり、必ず入らないという保証はありません。 どのようにパディングが入るかは環境依存になってしまうため、このパディングが入らないように書き込むと次のようなコードになります。 (エラー処理は省いています)

fwrite(&(a.b), sizeof(a.b), 1, fp);
fwrite(&(a.c), sizeof(a.c), 1, fp);

これはメンバ変数が増えると非常に面倒です。もう少し簡単な方法はないでしょうか?

Restriction

  • C++0xの機能は普及するまでもうしばらく時間がかかりそうなので、使いません
  • boostは使えない状況もあるため、使いません(お客さんの環境で、とか)

Solution1

最初は、char aとint bを連結したバイト配列に一度コピーする関数(toBinStr)を作って、
その関数の実行結果を一気に書き出す方法を考えました。
(さらにエンディアンも揃えられるとなお良いですが、今回は省略します)
const char *toBinStr(char a, int b);
const char *bstr = toBinStr(a.b, a.c);

さて実際に書き出そうとしてみるとバイト配列のサイズが必要なことに気がつきます。 strlenは最後にヌル文字がないので効果がありません。 多値を返せれば、と思うのですがそういった機能はC++にはなく、boost::tieやtupleも制限事項の関係から使えません。他の方法はstd::pairを使ったり、構造体を使うことです。 でもちょっとだけ構造体の名前を知ってもらったり、std::pairで戻り値を受けたりするのは面倒な気がしました。

Solution 2

解決策1を関数ではなくオブジェクトにしてみました。
BinaryString bstr(a.b, a.c);
fwrite(bstr.data(), 1, bstr.size(), fp);
これならまぁなんとか許容範囲です。
けれど、これでは引数が2つに固定でそれぞれchar型, int型に固定されてしまっています

Solution 3

テンプレートを使うことで任意の型に対応してみました。

class BinaryString {
  unsigned char *str_;
  unsigned int size_;

  template <class T>
  inline void memorycopy(T t){
    memcpy(str_ + size_, reinterpret_cast<void *>(&t), sizeof(T));
    size_ += sizeof(T);
  }

public:
  template <class T1>
  BinaryString(T1 t1) : size_(0) {
    str_ = new unsigned char[sizeof(T1)];
    memorycopy(t1);
  }

  template <class T1, class T2>
  BinaryString(T1 t1, T2 t2) : size_(0) {
    str_ = new unsigned char[sizeof(T1) + sizeof(T2)];
    memorycopy(t1);
    memorycopy(t2);
  }

  ~BinaryString(){ delete[] str_; };

  const void *data() const {
    return reinterpret_cast<void *>(str_);
  }

  unsigned int size() const {
    return size_;
  }

};
これでは引数が1つか2つの場合にしか対応されません。
可変長の引数に対応したいところです。
けれどstdarg.hを使ってしまうと型安全性が保たれません。
他にいい方法はないのでしょうか?

Solution 4

最初はテンプレートメタプログラミングを上手く使ってなんとか解決できないものか、と思ったのですが、難しそうです。
そのため、仕方なくプリプロセサメタプログラミングを使うことで解決しました。
マクロというと嫌な顔される人も多いと思いますが、状況を限定し、使い易いライブラリを書くためであれば必要に応じて使ってもよいのではないでしょうか?
勿論アプリケーションコードでは使用するべきではありませんが、boost::lambdaなんかでも部分的に利用されています。
//for memcpy
#include <cstring>

// for inner loop
#define BSTR_PP_REPEAT_I(N, FUNC) BSTR_PP_REPEAT_II(N,FUNC)
#define BSTR_PP_REPEAT_II(N, FUNC) BSTR_PP_REPEAT_I_ ## N(FUNC,0)
#define BSTR_PP_REPEAT_I_1(FUNC,NOT_TAIL) FUNC(NOT_TAIL, 1)
#define BSTR_PP_REPEAT_I_2(FUNC,NOT_TAIL) BSTR_PP_REPEAT_I_1(FUNC,1) FUNC(NOT_TAIL, 2)
#define BSTR_PP_REPEAT_I_3(FUNC,NOT_TAIL) BSTR_PP_REPEAT_I_2(FUNC,1) FUNC(NOT_TAIL, 3)
#define BSTR_PP_REPEAT_I_4(FUNC,NOT_TAIL) BSTR_PP_REPEAT_I_3(FUNC,1) FUNC(NOT_TAIL, 4)
#define BSTR_PP_REPEAT_I_5(FUNC,NOT_TAIL) BSTR_PP_REPEAT_I_4(FUNC,1) FUNC(NOT_TAIL, 5)
#define BSTR_PP_REPEAT_I_6(FUNC,NOT_TAIL) BSTR_PP_REPEAT_I_5(FUNC,1) FUNC(NOT_TAIL, 6)
#define BSTR_PP_REPEAT_I_7(FUNC,NOT_TAIL) BSTR_PP_REPEAT_I_6(FUNC,1) FUNC(NOT_TAIL, 7)
#define BSTR_PP_REPEAT_I_8(FUNC,NOT_TAIL) BSTR_PP_REPEAT_I_7(FUNC,1) FUNC(NOT_TAIL, 8 )
#define BSTR_PP_REPEAT_I_9(FUNC,NOT_TAIL) BSTR_PP_REPEAT_I_8(FUNC,1) FUNC(NOT_TAIL, 9)
#define BSTR_PP_REPEAT_I_10(FUNC,NOT_TAIL) BSTR_PP_REPEAT_I_9(FUNC,1) FUNC(NOT_TAIL, 10)
#define BSTR_PP_REPEAT_I_11(FUNC,NOT_TAIL) BSTR_PP_REPEAT_I_10(FUNC,1) FUNC(NOT_TAIL, 11)
#define BSTR_PP_REPEAT_I_12(FUNC,NOT_TAIL) BSTR_PP_REPEAT_I_11(FUNC,1) FUNC(NOT_TAIL, 12)
#define BSTR_PP_REPEAT_I_13(FUNC,NOT_TAIL) BSTR_PP_REPEAT_I_12(FUNC,1) FUNC(NOT_TAIL, 13)
#define BSTR_PP_REPEAT_I_14(FUNC,NOT_TAIL) BSTR_PP_REPEAT_I_13(FUNC,1) FUNC(NOT_TAIL, 14)
#define BSTR_PP_REPEAT_I_15(FUNC,NOT_TAIL) BSTR_PP_REPEAT_I_14(FUNC,1) FUNC(NOT_TAIL, 15)
#define BSTR_PP_REPEAT_I_16(FUNC,NOT_TAIL) BSTR_PP_REPEAT_I_15(FUNC,1) FUNC(NOT_TAIL, 16)
#define BSTR_PP_REPEAT_I_17(FUNC,NOT_TAIL) BSTR_PP_REPEAT_I_16(FUNC,1) FUNC(NOT_TAIL, 17)
#define BSTR_PP_REPEAT_I_18(FUNC,NOT_TAIL) BSTR_PP_REPEAT_I_17(FUNC,1) FUNC(NOT_TAIL, 18)
#define BSTR_PP_REPEAT_I_19(FUNC,NOT_TAIL) BSTR_PP_REPEAT_I_18(FUNC,1) FUNC(NOT_TAIL, 19)
#define BSTR_PP_REPEAT_I_20(FUNC,NOT_TAIL) BSTR_PP_REPEAT_I_19(FUNC,1) FUNC(NOT_TAIL, 20)

// for outer loop
#define BSTR_PP_REPAT_O(N, FUNC) BSTR_PP_REPAT_OO(N,FUNC)
#define BSTR_PP_REPAT_OO(N, FUNC) BSTR_PP_REPAT_O_ ## N(FUNC,0)
#define BSTR_PP_REPAT_O_1(FUNC,NOT_TAIL) FUNC(NOT_TAIL, 1)
#define BSTR_PP_REPAT_O_2(FUNC,NOT_TAIL) BSTR_PP_REPAT_O_1(FUNC,1) FUNC(NOT_TAIL, 2)
#define BSTR_PP_REPAT_O_3(FUNC,NOT_TAIL) BSTR_PP_REPAT_O_2(FUNC,1) FUNC(NOT_TAIL, 3)
#define BSTR_PP_REPAT_O_4(FUNC,NOT_TAIL) BSTR_PP_REPAT_O_3(FUNC,1) FUNC(NOT_TAIL, 4)
#define BSTR_PP_REPAT_O_5(FUNC,NOT_TAIL) BSTR_PP_REPAT_O_4(FUNC,1) FUNC(NOT_TAIL, 5)
#define BSTR_PP_REPAT_O_6(FUNC,NOT_TAIL) BSTR_PP_REPAT_O_5(FUNC,1) FUNC(NOT_TAIL, 6)
#define BSTR_PP_REPAT_O_7(FUNC,NOT_TAIL) BSTR_PP_REPAT_O_6(FUNC,1) FUNC(NOT_TAIL, 7)
#define BSTR_PP_REPAT_O_8(FUNC,NOT_TAIL) BSTR_PP_REPAT_O_7(FUNC,1) FUNC(NOT_TAIL, 8 )
#define BSTR_PP_REPAT_O_9(FUNC,NOT_TAIL) BSTR_PP_REPAT_O_8(FUNC,1) FUNC(NOT_TAIL, 9)
#define BSTR_PP_REPAT_O_10(FUNC,NOT_TAIL) BSTR_PP_REPAT_O_9(FUNC,1) FUNC(NOT_TAIL, 10)
#define BSTR_PP_REPAT_O_11(FUNC,NOT_TAIL) BSTR_PP_REPAT_O_10(FUNC,1) FUNC(NOT_TAIL, 11)
#define BSTR_PP_REPAT_O_12(FUNC,NOT_TAIL) BSTR_PP_REPAT_O_11(FUNC,1) FUNC(NOT_TAIL, 12)
#define BSTR_PP_REPAT_O_13(FUNC,NOT_TAIL) BSTR_PP_REPAT_O_12(FUNC,1) FUNC(NOT_TAIL, 13)
#define BSTR_PP_REPAT_O_14(FUNC,NOT_TAIL) BSTR_PP_REPAT_O_13(FUNC,1) FUNC(NOT_TAIL, 14)
#define BSTR_PP_REPAT_O_15(FUNC,NOT_TAIL) BSTR_PP_REPAT_O_14(FUNC,1) FUNC(NOT_TAIL, 15)
#define BSTR_PP_REPAT_O_16(FUNC,NOT_TAIL) BSTR_PP_REPAT_O_15(FUNC,1) FUNC(NOT_TAIL, 16)
#define BSTR_PP_REPAT_O_17(FUNC,NOT_TAIL) BSTR_PP_REPAT_O_16(FUNC,1) FUNC(NOT_TAIL, 17)
#define BSTR_PP_REPAT_O_18(FUNC,NOT_TAIL) BSTR_PP_REPAT_O_17(FUNC,1) FUNC(NOT_TAIL, 18)
#define BSTR_PP_REPAT_O_19(FUNC,NOT_TAIL) BSTR_PP_REPAT_O_18(FUNC,1) FUNC(NOT_TAIL, 19)
#define BSTR_PP_REPAT_O_20(FUNC,NOT_TAIL) BSTR_PP_REPAT_O_19(FUNC,1) FUNC(NOT_TAIL, 20)

#define BSTR_PP_IIF(p,t,f) BSTR_PP_IIF_2(p,t,f)
#define BSTR_PP_IIF_2(p,t,f) BSTR_PP_IIF_ ## p(t,f)
#define BSTR_PP_IIF_0(t,f) f
#define BSTR_PP_IIF_1(t,f) t

#define BSTR_PP_COMMA() ,
#define BSTR_PP_EMPTY()
#define BSTR_PP_COMMA_IF(p) BSTR_PP_COMMA_IF_2(p)
#define BSTR_PP_COMMA_IF_2(p) BSTR_PP_IIF(p, BSTR_PP_COMMA, BSTR_PP_EMPTY)()

#define BSTR_PP_CLASS(NOT_TAIL, i) class T##i BSTR_PP_COMMA_IF(NOT_TAIL)
#define BSTR_PP_ARG(NOT_TAIL, i) T##i t##i BSTR_PP_COMMA_IF(NOT_TAIL)
#define BSTR_PP_ADD(NOT_TAIL, i) BSTR_PP_IIF(NOT_TAIL, sizeof(T##i)  +, sizeof(T##i))

#define BSTR_PP_MEMCPY(NOT_TAIL, i) memorycopy(t##i);

#define BSTR_PP_METHOD(NOT_TAIL, i)                          \
  template < BSTR_PP_REPEAT_I(i, BSTR_PP_CLASS) >                   \
  BinaryString( BSTR_PP_REPEAT_I(i, BSTR_PP_ARG) ) : size_(0)       \
  {                                                 \
    str_ = new unsigned char[BSTR_PP_REPEAT_I(i, BSTR_PP_ADD)]; \
    BSTR_PP_REPEAT_I(i, BSTR_PP_MEMCPY)                         \
  }

class BinaryString {
  unsigned char *str_;
  unsigned int size_;

  template <class T>
  inline void memorycopy(T t){
    memcpy(str_ + size_, reinterpret_cast<void *>(&t), sizeof(T));
    size_ += sizeof(T);
  }

public:

  BSTR_PP_REPAT_O(20, BSTR_PP_METHOD)

  ~BinaryString(){ delete[] str_; };

  const void *data() const {
    return reinterpret_cast<void *>(str_);
  }

  unsigned int size() const {
    return size_;
  }

};

#undef BSTR_PP_IIF
#undef BSTR_PP_IIF_0
#undef BSTR_PP_IIF_1

#undef BSTR_PP_REPEAT_I
#undef BSTR_PP_REPEAT_I_1
#undef BSTR_PP_REPEAT_I_2
#undef BSTR_PP_REPEAT_I_3
#undef BSTR_PP_REPEAT_I_4
#undef BSTR_PP_REPEAT_I_5
#undef BSTR_PP_REPEAT_I_6
#undef BSTR_PP_REPEAT_I_7
#undef BSTR_PP_REPEAT_I_8
#undef BSTR_PP_REPEAT_I_9
#undef BSTR_PP_REPEAT_I_10
#undef BSTR_PP_REPEAT_I_11
#undef BSTR_PP_REPEAT_I_12
#undef BSTR_PP_REPEAT_I_13
#undef BSTR_PP_REPEAT_I_14
#undef BSTR_PP_REPEAT_I_15
#undef BSTR_PP_REPEAT_I_16
#undef BSTR_PP_REPEAT_I_17
#undef BSTR_PP_REPEAT_I_18
#undef BSTR_PP_REPEAT_I_19
#undef BSTR_PP_REPEAT_I_20

#undef BSTR_PP_REPAT_O
#undef BSTR_PP_REPAT_O_1
#undef BSTR_PP_REPAT_O_2
#undef BSTR_PP_REPAT_O_3
#undef BSTR_PP_REPAT_O_4
#undef BSTR_PP_REPAT_O_5
#undef BSTR_PP_REPAT_O_6
#undef BSTR_PP_REPAT_O_7
#undef BSTR_PP_REPAT_O_8
#undef BSTR_PP_REPAT_O_9
#undef BSTR_PP_REPAT_O_10
#undef BSTR_PP_REPAT_O_11
#undef BSTR_PP_REPAT_O_12
#undef BSTR_PP_REPAT_O_13
#undef BSTR_PP_REPAT_O_14
#undef BSTR_PP_REPAT_O_15
#undef BSTR_PP_REPAT_O_16
#undef BSTR_PP_REPAT_O_17
#undef BSTR_PP_REPAT_O_18
#undef BSTR_PP_REPAT_O_19
#undef BSTR_PP_REPAT_O_20

#undef BSTR_PP_COMMA
#undef BSTR_PP_EMPTY
#undef BSTR_PP_COMMA_IF
#undef BSTR_PP_COMMA_IF_2

#undef BSTR_PP_CLASS
#undef BSTR_PP_ARG
#undef BSTR_PP_ADD

#undef BSTR_PP_MEMCPY
#undef BSTR_PP_METHOD

#include <stdio.h>
void PrintBinary(const void *str, size_t size){
  for(size_t i = 0; i < size; i++){
    printf("%02x", *(reinterpret_cast<const unsigned char *>(str) + i));
  }
  printf("\n");
}

int main(int argc, char *argv[])
{
  BinaryString a(0.1, 'a');
  PrintBinary(a.data(), a.size());
  return 0;
}
これで任意個(20個までの制限はありますが)の引数を取る関数を書くことができました。
めでたしめでたし。
長くなった(& 遅くなった)ので参考文献及びプリプロセサの説明は次回。プリプロセサの定義はほぼboost::preprocessorからの抜粋です。いろいろ勉強になりました・・・。