読者です 読者をやめる 読者になる 読者になる

algoNote

プログラミング関連

デバッグ用のマクロ(C++)を工夫する

僕は競プロをやるときにいわゆるprintfデバッグをよくするのですが、その時にマクロが大活躍します。

普段使っているdump(x)

#define dump(x) cerr<<#x<<"="<<x<<endl

#xで変数名を文字列として扱えるという機能です。 このマクロがあるとdump(a)やdump(str)で変数aや文字列strの内容を分かりやすく表示できます。

これはこれで簡潔でよいのですが複数の値を並べて確認したいときは少し不便です。 例えば dump(a,b,c) で "a=0, b=1, c=2" のように出来れば便利そうです。

可変長引数を扱う

マクロで任意の個数の引数を取るには

#define P(...) printf(__VA_ARGS__)

のように...__VA_ARGS__を使うことで実現できます。 これを使ってなんとか個々の変数名とその内容を出力したい。

ググってみたのですが、残念ながら可変長引数の任意の引数にアクセスすることはできないようです。

#define dump(...) cerr<<#__VA_ARGS__<<"="<<__VA_ARGS__<<endl

もちろんこんなことをしても目的の動作はしてくれません。

テンプレート

さらにググっているとC++のテンプレートで可変長引数の関数が作れることが分かりました。(マクロにこだわる必要はない!)

具体的には

void func() {}  // ダミー
template<class First, class... Rest>
void func(const First& first, const Rest&... rest) {
    cout<<first<<",";
    func(rest...);
}

このように再帰的に引数を扱えます。(この例では引数を順番に出力しています。)

この関数の中で最初のdump(x)を使えば良さそうですがダメです。first=xxxのようになってしまいます。

文字列化

僕がやりたいことは

  • 変数名と変数の内容を表示したい
  • 任意の数の引数で実現したい

ここまでで分かったことは

  • 変数名を扱うにはマクロを使うしかなさそう
  • マクロで可変長引数を扱うには__VA_ARGS__を使う
  • 可変長引数の関数だと再帰的に引数を処理できる

です。

しばらく実験していると

#define hoge(...) #__VA_ARGS__
int a=1,b=2,c=3;
cout<<hoge(a,b,c)<<endl;

で、"a,b,c"が出力されることに気付きました。 #__VA_ARGS__で括弧の中身をそのまますべて文字列化出来るという訳です。

これを利用すれば、コロンの直前に"=%d"などを追加してprintfに渡すことで実現できそうです!

string get_str(string s) {
    s+=',';
    string ret="";
    for(int i=0; i<s.size(); i++) {
        if(s[i]==',') ret+="=%d, ";
        else ret+=s[i];
    }
    return ret;
}
#define dump(...) printf(get_str(#__VA_ARGS__).c_str(),__VA_ARGS__)

int main() {
    int a=1,b=2,c=3;
    dump(a,b,c);  // 出力:"a=1, b=2, c=3, "
}

一見良さそうですが、

  • 整数型にしか対応できない
  • dump(max(a,max(b,c)))が正常に動かない

という問題があります。(後者はget_strを弄ることで解決できますが)

変数の内容も自分で文字列化してしまう

c++のstringstreamを使うと変数の内容を文字列として得ることができます。 次に、これを使って、変数の内容を文字列化する関数を作ることを考えました。

ここで先程のテンプレートを使った可変長引数の関数が活躍します。 具体的には

queue<string> values;
void func() {}
template<class First, class... Rest>
void func(const First& first, const Rest&... rest) {
    stringstream ss;
    ss<<first;
    values.push(ss.str());
    func(rest...);
}

とすることで文字列化された変数の内容を得ることができます。

完成

ということで試行錯誤の末完成したのが次の形です

void set_args_con() {}
template<class First, class... Rest>
void set_args_con(const First& first, const Rest&... rest) {
    stringstream ss;
    ss<<first;
    args_con.push(ss.str());
    set_args_con(rest...);
}
string gen_string(string s) {
    s+=',';
    string ret="";
    int par=0;
    for(int i=0; i<s.size(); i++) {
        if(s[i]=='(' || s[i]=='<' || s[i]=='{') par++;
        else if(s[i]==')' || s[i]=='>' || s[i]=='}') par--;
        if(par==0 && s[i]==',') ret+="="+args_con.front()+", ",args_con.pop();
        else ret+=s[i];
    }
    return ret;
}
#define dump(...) set_args_con(__VA_ARGS__);cerr<<gen_string(#__VA_ARGS__)<<endl

int main() {
    int a=1,b=2,c=3;
    string str="Hello,World!";
    dump(a,b,c,str,max(a,b));
    // 出力 : "a=1, b=2, c=3, str=Hello,World!, max(a,b)=2, "
    return 0;
}

無駄が多そうな気はしますが一応やりたいことはできました。 (もっと簡単な方法があれば是非教えてください!!)

ただのdumpマクロに21行も使って見た目があまり良くないですが、前回作ったAtomプラグインのスキップ機能を使えば、コンテストの提出時には削ることが出来るので提出コードを汚すことはありません^^

参考にした記事