デバッグ用の可変長引数関数を作ってみた(C++)
目標の形
int main(){ int abc=10; int hoge=5; string str="aiueo"; debug(abc,hoge,str); } // 出力結果 abc:10 hoge:5 str:aiueo
debugはマクロでも関数でもなんでも良いから変数名とその値を空白区切りで出力してほしかった(できれば標準エラー出力)
結論
#define debug(...) debug_func(0, #__VA_ARGS__, __VA_ARGS__) template <typename T> void debug_func(int i, T name) { cerr << endl; } template <typename T1, typename T2, typename... T3> void debug_func(int i, const T1 &name, const T2 &a, const T3 &...b) { for ( ; name[i] != ',' && name[i] != '\0'; i++ ) cerr << name[i]; cerr << ":" << a << " "; debug_func(i + 1, name, b...); }
2つの関数とマクロを定義することで実現できた
改行の手前に余分な空白が入るのはまあデバッグ用だし...
それぞれの説明
まずは
#define debug(...) debug_func(0, #__VA_ARGS__, __VA_ARGS__)
から
これは可変長引数をマクロで読み取っている
なんでマクロなのかというと変数名を表示するのに#
演算子が必要だったから
※#
演算子で変数名を文字列として取得できる
例として、debug(a,bb,ccc)
と呼び出すと、"a,bb,ccc"
という文字列が取得できるので変数名を出力するときは,
を読み飛ばす必要がある
次に
template <typename T> void debug_func(int cnt, T name) { cerr << endl; }
これはdebug_func
の終了のための関数
最後の関数と名前は同じだけど引数が違う
再帰的に呼び出しながら出力していって、最後に改行するという流れ
最後に
template <typename T1, typename T2, typename... T3> void debug_func(int i, const T1 &name, const T2 &a, const T3 &...b) { for ( ; name[i] != ',' && name[i] != '\0'; i++ ) cerr << name[i]; cerr << ":" << a << " "; debug_func(i + 1, name, b...); }
この関数を見た人は全員次のような疑問ですえ!?ってなる(と思う)
Q.#define debug(...) debug_func(0, #__VA_ARGS__, __VA_ARGS__)
だと引数が3つなのにこれは引数が4つだから間違ってるんじゃないか?
A.間違ってないです
実は可変長引数を関数の引数に取るときは...
(pack演算子)を使える
pack演算子は前につけるか後につけるかで変わってくる
前につける
int ...b
可変長引数を一つの変数に受け取る後につける
b...
変数から可変長引数を取り出す
可変長引数の内、一つを変数に入れて残りをpack演算子でもう一つの変数にまとめているイメージ
int int_sum() { return 0; } template <typename T1, typename... T2> int int_sum(const T1 &a, const T2&... b){ return a + int_sum(b...); } int main(){ cout<<int_sum(10,20,5)<<endl; } // 結果 35
また、下記のように#define sum(...) int_sum(__VA_ARGS__)
のようにして呼び出しても良い
#define sum(...) int_sum(__VA_ARGS__) int int_sum() { return 0; } template <typename T1, typename... T2> int int_sum(const T1 &a, const T2&... b){ return a + int_sum(b...); } int main(){ cout<<sum(10,20,5)<<endl; }
余談
for ( ; name[i] != ',' && name[i] != '\0'; i++ ) cerr << name[i];
↑この部分は変数名が○○,○○○,....
という風にコンマ区切りで渡されるのでコンマまで一文字ずつ出力している
name
の型を調べてみるとconst char*
だったので最後に\0
がある
#include <iostream> #include <typeinfo> using namespace std; #define debug(...) debug_func(0, #__VA_ARGS__, __VA_ARGS__) template <typename T> void debug_func(int i, T name) { cerr << endl; cerr << "typename:" << typeid(name).name() << endl; cerr << "const char* same?:" << boolalpha << (typeid(name) == typeid(const char *)) << endl; } template <typename T1, typename T2, typename... T3> void debug_func(int i, const T1 &name, const T2 &a, const T3 &...b) { for ( ; name[i] != ',' && name[i] != '\0'; i++ ) cerr << name[i]; cerr << ":" << a << " "; debug_func(i + 1, name, b...); } int main() { int hoge = 10; int foo = 101391; long long hofoo = 101010; debug(hoge, foo, hofoo); }
hoge:10 foo:101391 hofoo:101010 typename:PKc const char* same?:true
typename
がPKc
となっているのはP
がポインタ、K
がconst
、c
がchar
を表していて、mangled nameと呼ばれているらしい
参考
詳しくはこちらをご覧ください