Posted at

書式付き文字列の実装方法多すぎとちゃうか?

More than 5 years have passed since last update.

書式付き文字列の関数を実装する方法色々。

他にも実装方法があったりするが、使った事あるのだけ。


可変数引数

cでもc++でも動かしたいならこんな感じ。

特徴としては

・メモリリークが怖い、危険

・書式を間違えると何も表示されないし、無駄に引数を増やすと無視されるし、順番が分かりにくい

・桁数指定などのフォーマットが覚え易いかも

・自分の場合たまにしか実装しないので、毎回実装方法を忘れる

・複数の可変数引数を持つことは出来ないので、書式付き文字列AとBを比較する関数が書けない

・デフォルト引数と両立出来ない

#include <iostream>

#include <stdarg.h>

void log(const char* format, ...)
{
va_list ap;
va_start(ap, format);
char* buffer;
vasprintf(&buffer, format, ap);
va_end(ap);

std::cout << buffer << std::endl;
free(buffer);
}

int main()
{
log("1+1=%d", 1 , "あ" , 0.1 );
}

参考1

参考2


Boost.Format

参考

リンク先の方が詳しい

・型指定しなくて良いので安全だし、楽

・フォーマット指定がやや豊富

・一応printfの代替手段であるため、引数をstd::stringにする実装になり関数の利用者がboost::formatと書く必要がある(初期化子リストでなんとか出来そう?)

・const char*やstd::stringが引数になっている関数を再実装せずに可変数引数っぽく使える

・可変数引数ではないので、デフォルト引数や複数の書式付き文字の比較も可能


可変数引数テンプレート

c++11で追加されたあれで実装。

・安全だし、順番が分かりやすい

・フォーマット指定はiomanipのstd::setwとかを使う、多機能だけど冗長かも

・可変数引数なので、デフォルト引数や書式付き文字列同士の比較関数は書けない

・テンプレートなので仮想関数に使えない

・テンプレートの展開量が気になる場合、可変数引数を文字列に変換する関数を使いまわそう。

参考

#include <iostream>

#include <string>
#include <boost/format.hpp>
#include <stdarg.h>

template <class TLast>
void Log(TLast last)
{
std::cout << last;
}
template < class TFirst, typename ... TRest>
void Log(TFirst first, TRest... rest)
{
std::cout << first;
Log(rest...);
}

int main()
{
Log( "1+1" , "=" , 1);
}


可変数引数テンプレートで初期化可能な型を引数にする

 例えばstd::maxでは、複数の値の最大値を返すのに、可変数引数を使わずコンテナを引数にして初期化子リストを使っている。(配列だっけ?)

 この考え方を応用して、可変数引数コンストラクタを持つ文字列型を引数にすると可変数引数っぽくなる。

 実装例はサンプルなので、エラー処理とかは演算子のオーバーロードとか端折りまくってます。

可変数引数コンストラクタを持っていれば良いだけなので、実装方法の自由度は高い。

特徴は

・安全(実装にva_listとか使うと危険にも出来る)

・1回VariadicStreamに相当する型を実装すれば、以降可変数引数の処理を実装する必要がない

・今回はiomanip想定だけど書式指定をboost::format風にしたりする事も可能なはず

・ちゃんと実装しようとすると結構面倒だし分かりにくい、下の実装だとstrで文字列をコピーしてたり云々

・仮想関数OK、デフォルト引数OK、書式付き文字列同士の比較もOK

・仮引数に{}が必要なのが冗長かも?

・書式付き文字列を指定した座標に指定した色で描画する関数等だと、どの部分が可変数引数かが分かりやすいのはメリットにもなる

#include <iostream>

#include <string>
#include <sstream>
#include <iostream>

class VariadicStream
{
private:
std::ostringstream ost;

public :

template < typename ... TStream>
VariadicStream( TStream... tst)
{
Change(tst...);
}

std::string str()
{
return ost.str();
}

template < class TLast>
void Change(TLast tlast)
{
ost << tlast;
}

template < class TFirst, typename ... TRest>
void Change(TFirst tf, TRest... trest)
{
ost << tf;
Change( trest...);
}
};

void Log(VariadicStream vst)
{
std::cout << vst.str();
}

int main()
{
Log( {"1+1" , "=" , 2} );
}

 余談ですが、boost::anyの配列の配列を引数にすると、可変数引数が可変数個あるみたいな事も出来るとかなんとか。誰かが変な用途に使ってそう。

 個人的にはboost::formatと最後の方法をよく使う。