C++11 Advent Calendarの参加記事です。
static_assertの利用例とdynamic_assert(仮)についてを記述します。
次のような仕様の"send_foo()"関数を作ることにする。
上記仕様をできるだけ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には渡せないので、そうやりたい場合は
いまのところ標準にはそういうモノがなさそうなので自前で定義することになる。
power関数の仕様は以下
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");
}
結果はこちら

実際にはpower関数は車輪なので、Sprout C++ Libraryなどを使わせてもらう。
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
結果はこちら

外部の関数実行のテストはやりずらい。そのため、
まず実行するべき関数を配列に入れておき、
どれを実行するかを選ぶ新たな関数を作り、そいつに対してテストを行う。
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()関数で、実行するべき関数のを選ぶ。仕様は次のとおり。
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()メソッドを使って計算するように仕様が変更される。無情な世の中である。
vectorなどを使われてしまうと、static_assertができない。その場合boost::testなどを使った通常のテストを行う必要がある。
しかし、static_assertと比べ、通常のテストには以下のような弱点がある

そのため、上記機能を実現するVisual Studioのアドインを開発中。dynamic_assert(仮)とした。
ソースとバイナリのダウンロードはこちら: DynamicAssertAddin.zip
アドインをVisual Studioに正常に追加できている場合、ツールメニューに「Dynamic Assertの開始」と
表示される。プロジェクトを開いてからそのメニューを選択すると、次のようになる。

現在はプロジェクト実行時にboost::testが走るVC++のプロジェクトのみ対応しています。「裏で走らせる」機能を、Jenkinsみたいな別のサーバーにやらせることができれば、大規模開発にも耐えると思う。
要するに、infinitestのパクリ。
以上です
トラックバックURL: http://www.lunaport.net/mt/mt-tb.cgi/34
コメントする