2011年12月16日

static_assertとdynamic_assert(仮)

C++11 Advent Calendarの参加記事です。

static_assertの利用例とdynamic_assert(仮)についてを記述します。

1 static_assert利用例

次のような仕様の"send_foo()"関数を作ることにする。

  1. 任意のPlain Old Data型変数を引数として受けとる
  2. sizeof(引数)の値が2の10乗を超えたらエラー
  3. sizeof(引数)の値を、2のN乗かを計算し、すでに存在する関数send_foo_N()を実行

上記仕様をできるだけstatic_assertでチェックしてみる。

1.1 「sizeof(引数)の値が2の10乗を超えたらエラー」のstatic_assert

次のようにする

template <class T>
void send_foo(T t) {
	static_assert(sizeof(t) <= (2 << (10 - 1)), "sizeof(t) is too big");
}

ただ、シフト演算よりもpow(2, 10)と書くほうがベターではある。
しかし、標準のpow関数の戻り値はstatic_assertには渡せないので、そうやりたい場合は
いまのところ標準にはそういうモノがなさそうなので自前で定義することになる。

1.1.1 テストとpower関数の自作

power関数の仕様は以下

  1. 引数1を引数2乗した結果を返す
  2. 引数、戻り値はint、ただし負の数を与えた場合と0の0乗は未定義
  3. 結果が符号あり32ビット整数の最大値を超えたら結果は未定義
static_assert(power(0, 1) == 0, "0の1乗");
static_assert(power(0, 2) == 0, "0の2乗");

static_assert(power(1, 0) == 1, "1の0乗");
static_assert(power(1, 1) == 1, "1の1乗");
static_assert(power(1, 2) == 1, "1の2乗");

static_assert(power(2, 0) == 1, "2の0乗");
static_assert(power(2, 1) == 2, "2の1乗");
static_assert(power(2, 2) == 4, "2の2乗");

// 最大値付近のチェックは略

実装を書く

constexpr int power(int x, int y) {
    return y <= 1 ? x : x * power(x, y - 1);
}

コンパイルしてもエラーが出ないことを確認して、元のsend_foo()関数を書き換える

template <class T>
void send_foo(T t) {
	static_assert(sizeof(t) <= power(2, 10), "sizeof(t) is too big");
}

結果はこちら
1.png

実際にはpower関数は車輪なので、Sprout C++ Libraryなどを使わせてもらう。

1.2 「任意のPlain Old Data型変数を引数として受けとる」のstatic_assert

type_traitsヘッダ内に、型を引数にとって、型の特性を教えてくれる機能いっぱいある。
これはstatic_assertでも使用することができる。
「Plain Old Dataかどうかを調べる」機能を呼び出すにはis_podを使う。

is_podはメタ関数と呼ばれていて、普通の関数とは違い下記のようにして結果を取得

bool PODか判定結果 = is_pod<PODかどうか調べたい型>::value;

ぜんぜん直観的ではないが、判定結果はstatic_assertに渡すことができる。

#include <type_traits>
template <class T>
void send_foo(T t) {
	static_assert(std::is_pod<T>::value, "t is not POD");
}

最初は嫌だったけど、自分自身が似たようなテクニックを使わざるを得ない状況に陥ると
だんだん作った人の気持ちがわかり、この表記も許せるようになった。

いちおうマクロを定義すれば、直観的な関数呼び出しの書き方が使えるけどあんましおすすめしない。

#define IS_POD(arg) is_pod<decltype(arg)>::value
template <class T>
void send_foo(T t) {
	static_assert(IS_POD(t), "T is not POD");
}

type_traitsについては、このサイトがめっちゃ詳しいです。

https://sites.google.com/site/cpprefjp/reference/type_traits

結果はこちら
2.png

1.3 「sizeof(引数)の値を、2のN乗かを計算し、すでに存在する関数send_foo_N()を実行」 のstatic_assert

外部の関数実行のテストはやりずらい。そのため、
まず実行するべき関数を配列に入れておき、
どれを実行するかを選ぶ新たな関数を作り、そいつに対してテストを行う。

template <class T>
void send_foo(T t) {
	static_assert(sizeof(t) <= power(2, 10), "sizeof(t) is too big");
	static_assert(std::is_pod<T>::value, "t is not POD");
    // 実行すべき関数
    std::function<void(void*)> functions[] = {
        send_foo_0,
        send_foo_1,
        send_foo_2,
        send_foo_3
        // TODO ここにいっぱい関数が増える。
    };
    // どれを実行するか選ぶ関数
    constexpr int index = get_index_by_size(sizeof(t));
    static_assert(index >= 0 && index < _countof(functions), "array index out of bounds");
    // 実行
    functions[index](reinterpret_cast<void*>(&t));
}

get_index_by_size()関数で、実行するべき関数のを選ぶ。仕様は次のとおり。

  1. 引数が、2のN乗かを計算し、Nは小数点値切捨てて結果として返す。(power(floor(log2(引数)), 2))
  2. 結果が符号あり32ビット整数の最大値を超えたら結果は未定義
static_assert(get_index_by_size(1) == 0, "");
static_assert(get_index_by_size(2) == 1, "");
static_assert(get_index_by_size(3) == 1, "");
static_assert(get_index_by_size(4) == 2, "");
static_assert(get_index_by_size(1023) == 9, "");
static_assert(get_index_by_size(1024) == 10, "");
// 最大値判定は略

実装を記述する。この実装にたどり着くまで30回くらいトライ&エラーをやった。
再帰関数は、正直人類には難しすぎる知恵だと思う。

constexpr int get_index_by_size(int size, int depth = 0) {
    return (size < power(2, depth)) ?
        depth : get_index_by_size(size, depth + 1);
}

そして、完成した時を見計らって、引数としてstd::vectorを渡し、計算で使ってたPODのサイズを
size()メソッドを使って計算するように仕様が変更される。無情な世の中である。

dynamic_assert(仮)について

vectorなどを使われてしまうと、static_assertができない。その場合boost::testなどを使った通常のテストを行う必要がある。

しかし、static_assertと比べ、通常のテストには以下のような弱点がある

  1. テスト結果を、IDEと連携して表示するツールがなぜか高価
  2. ソースを書いて保存しても自動でテストは走らない
    (最近のIDEはソース執筆中に自動で文法エラーとか簡単なstatic_assert結果を表示してくれる)

4.png

そのため、上記機能を実現するVisual Studioのアドインを開発中。dynamic_assert(仮)とした。
ソースとバイナリのダウンロードはこちら: DynamicAssertAddin.zip

アドインをVisual Studioに正常に追加できている場合、ツールメニューに「Dynamic Assertの開始」と
表示される。プロジェクトを開いてからそのメニューを選択すると、次のようになる。

  1. 裏で勝手にテストが走る
  2. エラー一覧に結果が表示される

5.png

現在はプロジェクト実行時にboost::testが走るVC++のプロジェクトのみ対応しています。「裏で走らせる」機能を、Jenkinsみたいな別のサーバーにやらせることができれば、大規模開発にも耐えると思う。

要するに、infinitestのパクリ。

以上です

 

コメントする





トラックバック(0)

トラックバックURL: http://www.lunaport.net/mt/mt-tb.cgi/34

るなぽブログ

最近のブログ記事

その他