連絡先 Hikwareホーム
Hikware.Tech
自分用の覚え書きをそのまま公開。参考程度にどうぞ。

可変長引数の有無でオーバーロードしたい

公開日 2017/11/08
最終更新 2017/11/08
  • 書式指定付きのログ出力などを作る場合、書式展開の必要がない文字列はそのまま出力したいので、可変長引数ありとなしでオーバーロードしてみたら曖昧だと怒られた。なんとかしたい。

そんくらい分かってよ!

 例えばログ出力関数を作るとして、まあ書式指定できた方が便利なので、書式と可変長引数を取る関数を作ったとする。

// フォーマットしてログを出力
void PutLog( const char* format, ... );

 で、この関数内で vsprintf なりでフォーマットして、最終的に各プラットフォームのログ出力関数に渡す訳だけど、書式展開が不要な場合は vsprintf を通さず直接出力するよう、引数なしの関数をオーバーロードしてみたところ、引数なしで呼んだら 「どっち呼んだらいいか分かんねえ!」 と怒られた。えぇぇ...?

// (A)書式指定なしの文字列はこっちで即出力
void PutLog( const char* text );
// (B)書式指定されたら展開してから(A)に渡す
void PutLog( const char* format, ... );


// 可変長引数があるなら当然(B)が呼ばれる
PutLog( "Result=%d", result );

// 可変長引数がない場合、(A)と(B)どっち呼べばいいか分からんと怒られる
PutLog( "Failed!" );   <- コンパイルエラー

 いやそんくらい分かってよ!、と嘆きたくなるものの、Visual Studio, Android Studio, Xcode ともに同じ反応なので、これを許可すると何かしら問題があるんかね。まあできないものは仕方ない。

ちょびっとC++11

 今開発してるアプリから、2017年にもなってやっと今更 C++11 を使い始めてるんだけど、C++11 の便利機能のひとつ、可変引数テンプレート ってのが使えそうな気はする。でも使い方が多岐すぎて、読めば読むほどコンパイル時に何がどこまでどう展開されるのか頭が追いつかなくて正直しんどい。再帰とかどう展開されるんだよ。

 なので、老いた頭でも容易に想像できる範囲で使ってみた結果、こうなった。

// テンプレートで可変長引数の個数を展開して出力関数に渡す
template <typename... Args>
void PutLog(
    const char* format, const Args... args)
{
    // 「args...」は渡された可変長引数がそのまま展開される
    // 「sizeof...(args)」は引数の数に展開される
    PutLogF( format, sizeof...(args), args... );
}


// 実際に出力する関数(クラスメソッドならprivateにしとくとよろし)
void PutLogF(
    const char* format, size_t argc, ...)
{
    if ( argc == 0 )
    {
        // 可変長引数なしなら各プラットフォームのログ出力関数にそのまま渡す
        PlatformLog( format );
    }
    else
    {
        // 可変長引数があるなら書式展開して出力。雑なコードでスマヌ
        char buf[1024];
        va_list args;
        va_start( args, argc );
        vsnprintf( buf, sizeof(buf), format, args );
        va_end( args );
        PlatformLog( buf );
    }
}

 点々多すぎィ!。でもこれならまあ、展開されてもそれほどコードも肥大化しなさそうだし、いいんじゃなかろうか。スマホアプリだとコードをコンパクトにする事も重要なので、テンプレートは便利だけどちょっと気を使う。

 それと、引数の数を自動取得できるのは地味に嬉しい。今後何かと使い道がありそう。

納得いかないけどもっと冴えた方法が

 で、いったんは上の方法で解決してたんだけど、後でもっと冴えた方法がある事に気付いた。可変引数テンプレートと同名の可変長引数なしの関数がある場合は、可変長引数なしで呼んだら素直に引数なしの方が呼ばれるぽい。
 これが明確な仕様なのか処理系依存なのかはよく分からんけど、Visual Studio, Android Studio, Xcode とも問題なし。これがアリなんだったら普通に可変長引数の有無でもオーバーロードさせてよ!

// 可変長引数なしの場合はこちらが呼ばれる
void PutLog(
    const char* text)
{
    // 各プラットフォームのログ出力関数
    PlatformLog( text );    
}


// 可変長引数がある場合はこちらが展開される
template <typename... Args>
void PutLog(
    const char* format, const Args... args)
{
    // 「args...」は渡された可変長引数がそのまま展開される
    // 要するに、PutLogF を名前を変えて呼んでるだけ
    PutLogF( format, args... );
}


// 実際に書式展開する関数(クラスメソッドならprivateにしとくとよろし)
void PutLogF(
    const char* format, ...)
{
    char buf[1024];
    va_list args;
    va_start( args, argc );
    vsnprintf( buf, sizeof(buf), format, args );
    va_end( args );
    PutLog( buf );
}

C++11 より前に編み出された黒魔術

 てことで C++11 のおかげでわりかし綺麗に解決したけど、C++11 以前の世界ではマクロを使っていろんな黒魔術が編み出されてたそうな。結局、いくら読んでもこのマクロの理屈はよく分からんかった。

 あと、型は固定になるけど、可変長引数の数を取得できる豪快すぎる方法に出会って笑った。その手があったかー。