はじめに
以前、「C++20/23 の導入効果 ~従来との比較~」 という記事を書きました。そこでは C++20 以降の新機能全般について概説し、従来のコードと比較しながらどのように開発効率やコード品質が向上するかを紹介しました。この記事では、文字列フォーマットを行うための std::format
をさらに詳細に解説してみます。
従来は printf
や std::stringstream
、または外部ライブラリ(特に有名なのが fmt)を用いてフォーマットするのが一般的でしたが、C++20で待望の標準ライブラリとして正式に文字列フォーマットを行うため std::format
が組み込まれました。
この記事では、std::format
の基本的な使い方からPython・C#のフォーマットとの違いまで、一通り解説していきます。よくある質問(FAQ)もまとめていますので、ぜひ参考にしてください。
1. std::format
とは?
C++20から <format>
ヘッダに追加された、文字列フォーマット用の関数やユーティリティの集合です。
主なポイントは以下のとおりです。
- フォーマット用の文字列(format string)に埋め込み
{}
を使い、引数を挿入できる。 -
Python の
str.format
スタイルをベースにしており、表記が直感的。 - C++17以前では外部ライブラリとして使われていた fmt ライブラリ が標準化された形。
-
<format>
にはstd::format
のほか、std::format_to
/std::format_to_n
/std::vformat
など、フォーマットに関する様々な関数が含まれる。
これにより、可読性と保守性を大幅に向上させながら、効率よく文字列を構築できるようになりました。
2. 基本的な使い方
2.1 最小の例
#include <format>
#include <iostream>
int main() {
int age = 30;
std::string name = "Taro";
// std::format(フォーマット文字列, 値...)
std::string message = std::format("My name is {} and I am {} years old.", name, age);
std::cout << message << std::endl; // My name is Taro and I am 30 years old.
return 0;
}
- フォーマット文字列内の
{}
部分に引数が順番通りに埋め込まれます。 - 戻り値は
std::string
なので、さらに加工して使うことも簡単です。
2.2 引数の順番やインデックス指定
Pythonと同じく、引数を {0}
, {1}
と番号付きで書くこともできます。
std::string s1 = std::format("({1}, {0})", "zero", "one");
// -> "(one, zero)"
ただし、省略時は渡した順に埋め込まれるので、特に順番を入れ替える必要がない場合は {}
だけでOKです。
3. PythonやC#との違い
3.1 Python の str.format
との比較
-
似ている点
-
{}
や{:<10}
のように、中かっこ内でアライメントやフィールド幅を指定する構文が共通。 - Pythonと同様に、
{0}
,{1}
などと番号を指定して引数を埋め込むことも可能。
-
-
異なる点
- Python は動的型付け言語なので、実行時にオブジェクト型が決まる。一方、C++ は静的型付け言語で、コンパイル時に型が明確になるため、より型安全に扱える。
- Python には文字列補間(f-string)
f"{name} {age}"
もあるが、C++ は埋め込み記法としてはstd::format
ベースのみ(将来的にリテラル埋め込み記法の提案も検討中)。
3.2 C# の string.Format
/文字列補間($"..."
)との比較
-
似ている点
-
string.Format("Name = {0}, Age = {1}", name, age)
と同じように、std::format("Name = {}, Age = {}", name, age)
でフォーマットが可能。 - 可変長引数をフォーマット文字列に埋め込む設計思想が類似。
-
-
異なる点
- C# の
$"Name = {name}, Age = {age}"
という糖衣構文(文字列補間)は、現時点で C++ 標準には存在しない。C++側はマクロや外部ライブラリを使って擬似的に実現する手法があるが、公式機能としてはまだ。 -
std::format
はfmt
ライブラリ由来であり、より細かい指定(2進数や基数の指定など)が豊富に用意されている。
- C# の
4. フォーマット指定の活用例
4.1 フィールド幅やアライメント
#include <format>
#include <iostream>
int main() {
std::cout << std::format("|{:<10}|{:>10}|", 123, 456) << std::endl;
// 左寄せ 10 桁: "123 "
// 右寄せ 10 桁: " 456"
// 出力: "|123 | 456|"
return 0;
}
-
{:<10}
は左寄せ+10桁幅指定、{:>10}
は右寄せ+10桁幅指定。
4.2 フィル文字の指定
#include <format>
#include <iostream>
int main() {
std::cout << std::format("|{:-<10}|{:*^10}|", 123, 456) << std::endl;
// '-' で左寄せ埋め、'*' で中央寄せ埋め
// 出力: "|123-------|***456****|"
return 0;
}
-
{:-<10}
: 左寄せ +-
を使って埋める -
{:*^10}
: 中央寄せ +*
を使って埋める
4.3 進数・基数指定など
#include <format>
#include <iostream>
int main() {
std::cout << std::format("Hex: {0:x}, Oct: {0:o}, Bin: {0:b}", 255) << std::endl;
// 出力: "Hex: ff, Oct: 377, Bin: 11111111"
return 0;
}
-
{:x}
で16進、{:o}
で8進、{:b}
で2進表示 - 大文字表示なら
{:X}
,{:O}
,{:B}
などを使うことも可能
5. std::format_to
/ std::format_to_n
で直接バッファに書き込む
std::format
は生成された文字列を返す関数ですが、直接バッファ(イテレータ)に書き込みたい場合は std::format_to
が便利です。
#include <format>
#include <vector>
#include <iostream>
int main() {
std::vector<char> buffer;
buffer.reserve(100);
// format_to(back_inserter, フォーマット文字列, 値...)
auto it = std::format_to(std::back_inserter(buffer),
"Hello, {}!", "World");
// null 終端を手動で追加 (C++20の段階では必要)
buffer.push_back('\0');
std::cout << buffer.data() << std::endl; // Hello, World!
return 0;
}
-
std::format_to
は文字列を直接イテレータに書き込み、文字列オブジェクトを生成しません。 - より細かい制御が必要な場合や、性能を最大限に引き出したい場合に便利です。
-
std::format_to_n
は、出力文字数を制限した上で書き込みたいときに使用します。
6. パフォーマンスについて
-
std::format
は内部的に効率よく文字列を組み立てるため、高パフォーマンスが期待できます。 -
std::stringstream
やsprintf
系関数よりも、安全かつ可読性が高いコードが書きやすいのも利点です。 - 実際にどれくらい速くなるかはケースバイケースなので、性能要件が厳しい場合はプロファイリングして検証することを推奨します。
7. 実際に使うには?
- C++20 対応のコンパイラ(GCC 10以上、Clang 11以上、MSVC 2019(バージョンによる)など)を使い、
-std=c++20
等のフラグを指定すれば<format>
が利用可能です。 - ただし、コンパイラや標準ライブラリの実装状態によってはサポートが不完全な場合もあるので、最新環境を使うと安心です。
- もし
<format>
が使えない場合、互換性がある fmt ライブラリ を導入する手もあります。APIの多くがstd::format
とほぼ同じインターフェースで提供されているため、移行も容易です。
8. まとめ
- C++20で登場した
std::format
は、従来の C++ 標準にはなかった強力な文字列フォーマット機能です。 - Python や C# と比較しても、ほぼ同等以上の書きやすさ・柔軟さを持ち、型安全・高パフォーマンスなコードが書けます。
- 今のところ C# の文字列補間
"$...{}..."
のような糖衣構文はありませんが、C++にも将来的な拡張が期待されています。 - フォーマット機能の活用で、可読性と保守性の向上、さらにパフォーマンス向上も狙えるので、プロジェクトで使ってみることをおすすめします!
9. よくある質問 (FAQ)
Q1. std::format
と fmt
ライブラリはほぼ同じものですか?
A. ほぼ同じです。 std::format
は fmt
ライブラリの機能をベースに標準化されたものなので、書き方や指定子も非常に似ています。若干異なる部分はあるものの、基本的な使い方は同じです。
また、fmt
の方が新機能対応が早かったり、C++20未満のコンパイラでも使えたりといった利点があるため、環境によっては fmt
を選択することも多いです。
Q2. std::format
でエラーが出る場合、どうすればいいですか?
A. 多くの場合、以下の原因が考えられます。
-
コンパイラのバージョンが古い:C++20に正式対応していない場合、
<format>
を使えないことがあります。 -
フラグの指定漏れ:
-std=c++20
などが指定されていない。 -
標準ライブラリの実装不備:libstdc++ / libc++ のバージョンが古い場合に発生します。
この場合、最新のコンパイラ・ライブラリにアップデートするか、fmt ライブラリを導入して回避する方法があります。
Q3. Python や C# のように、フォーマット文字列内で変数名を直接指定できますか?
A. 残念ながら、現時点の C++ 標準では変数名を直接書いて埋め込む(いわゆる文字列補間)はサポートされていません。
例えば C# の $"Name={name}"
や Python の f"{name}"
のような仕組みは、C++20 にはありません。
筆者はC#で、よく変数名を直接指定して埋め込んでおり、それが非常に便利に感じていました。std::format で対応できていないのが残念です。将来の提案や拡張で実現が検討されているようなので、今後の標準化に期待しています。
Q4. printf
や sprintf
と比べてメリットはなんですか?
A. 主に以下のメリットがあります。
-
型安全:
printf
系関数は書式指定子と引数の型が合っていないと未定義動作になるリスクがありますが、std::format
は型安全に処理されます。 -
可読性:
printf
の%d
,%s
などの指定子を逐一合わせる必要がなく、{}
と引数を並べるだけで良いのでコードが読みやすいです。 - 拡張性: 2進数表示や任意の埋め文字指定など、かなり柔軟なカスタマイズが可能です。
Q5. std::format_to
はどう使い分ければいいですか?
A. std::format_to
は、標準の std::format
が返す std::string
を受け取らずに、そのまま任意のバッファに書き込みたいときに使います。
- 文字列を大量に組み立てる場合など、パフォーマンスを最適化したい場面で役立ちます。
- 書き込み先を自分で制御できるため、大きなバッファを一度確保しておけば、メモリアロケーションを最小限に抑えられる可能性があります。
参考リンク
- C++ reference -
<format>
ライブラリ (cppreference.com) - fmt ライブラリ (GitHub)
- C++20 Features (CppCon 2019 などの講演資料)
ここまでご覧いただきありがとうございます。
もし疑問や補足があれば、コメントで教えていただけるとありがたいです。
以上