iostream や std::stringでprintfのような書式指定を行う方法 (C++11版)

C++11を使うと綺麗に実装できます
まず format() というテンプレート関数を作ります.C++11で新しく導入された,可変引数テンプレート,および std::snprintf() を使います.

#include <string>
#include <cstdio>
#include <vector>

template <typename ... Args>
std::string format(const std::string& fmt, Args ... args )
{
    size_t len = std::snprintf( nullptr, 0, fmt.c_str(), args ... );
    std::vector<char> buf(len + 1);
    std::snprintf(&buf[0], len + 1, fmt.c_str(), args ... );
    return std::string(&buf[0], &buf[0] + len);
}

あとはこれを呼ぶだけ.

#include <iostream>
int
main()
{
    std::cout << format("%e", 1./3)  << std::endl;
    return 0;
}


format()はstd::string を返すので .c_str()と組みわせることでメモリリークなしでc-stringを動的に生成することもできます

#include <iostream>

void echo(const char *str)
{
   printf("%s\n", str);
}

int
main()
{
    echo("Hello C++11");
    echo(format("%e", 1./3).c_str());   // c_str()をつかう
    return 0;
}

なお format()が返す std::string は一時オブジェクトなので、以下のコードは正常に動作しません

int
main()
{
    echo("Hello C++11");
    const char *s = format("%e", 1./3).c_str();
    // この時点で 一時オブジェクトは消滅しているので
    // ポインタ sが指し示すアドレスのメモリは無効になってる
    echo(s);
    return 0;
}

この場合は次のように書き換えると、正常に動作します

int
main()
{
    echo("Hello C++11");
    std::string str = format("%e", 1./3); // 一時オブジェクトを str にコピー
    const char *s = str.c_str();
    echo(s);
    return 0;
}

補足

C++ で "%d" のような書式指定を行うには boost::format を使う方法もあります

#include <iostream>
#include <boost/format.hpp>

int main()
{
    std::cout <<
        boost::format("%2% %1%") % 3 % std::string("Hello")
    << std::endl;
}

という感じになります.しかしこのコードは

  • 慣れてない人は全く読めない(悪い意味でboost的)
  • boost のインストールが少々面倒(boostのbcpを知っていれば少しは楽ですが,再配布や納品時,ライセンスまで考えるとやはり面倒)

という点が不便でした.

一方,C++11版なら,

  • コンパクト
  • 移植性もバッチリ(追加ライブラリ不要.visual studio, clang, gccどれでも標準ライブラリだけで動きます)

という感じで使い勝手がいいかと思います.