対象読者
- C++の最新バージョンの機能を把握したい方
- C++の経験者で、C++に改めて入門したい方
- プログラミング言語の最新パラダイムに関心のある方
必要な環境
本記事のサンプルコードは、以下の環境で動作を確認しています。なお、一部のサンプルは以下の環境では動作しないか、あるいは実験的実装(Experimental)なので動作が不安定になる可能性があります。
-
macOS Sequoia / Windows 11
- Xcode Command Line Tools 16.0(Clang 16.0.0)
- w64devkit 2.4.0(GCC 15.2.0)
主要コンパイラのC++対応状況およびC++プログラムのコンパイルについての説明は、第1回を参照してください。
出力関連の強化
C++ 23では、フォーマット付き出力のためのstd::print/std::println関数が使えるようになりました。
C++では、標準出力などに情報を書き出したい場合、高水準のストリーム出力であるcoutの利用が基本です(本連載もそれに倣っています)。しかしながら、書き出す値が増えたり、書式指定が必要な場合には、たくさんの「<<」演算子やマニュピレータ(出力形式を指定するオブジェクト)が必要になるなど、コードが長くなり可読性も下がる傾向があります。このため、できれば従来のprintf関数のようにシンプルに書式指定したいというニーズがありました。
簡潔でフォーマット可能な出力のためのstd::print/std::println[C++ 23]
C++ 23では、printf関数のような書式指定文字列を使った出力を行う、print/println関数が利用できるようになりました。両者の違いは、最後に改行文字を出力するかどうかです。これらの関数には、第1引数にストリームを受け取るものと、従来のファイルハンドルを受け取るオーバーロードがあります。
前者を使う場合には<iostream>を、後者を使う場合には<print>を、それぞれincludeします。書式指定文字列自体は共通なので、以降は<iostream>を使っていきます。
以下のリストは、"Hello, world!"+改行を得る出力を、それぞれの関数で記述したものです。
#include <iostream> #include <print> …略… print(cout, "Hello, world!\n"); println(cout, "Hello, world!"); print(stdout, "Hello, world!\n"); println(stdout, "Hello, world!");
std::formatによる書式指定[C++ 20]
print/println関数における書式指定自体は、C++ 20で利用できるようになったstd::format関数に準拠したものを使います。基本となるのは、値を埋め込む置換フィールド(プレースホルダー)です。
以下のように、中カッコ({ })を使って置換フィールドを指定します。中カッコに引数ID(位置パラメータ)を埋め込み、引数の位置を0からの数値で指定することもできます。このように、printf関数における%d、%sといったものではなく、多くのモダンなプログラミング言語で採用されているプレースフォルダを使うのが特徴です。
以下のリストは、置換フィールドの基本的な使用例です。位置パラメータのありとなしの例となっています。
auto x = 2, y = 3, z = 5;
println(cout, "{} + {} = {}", x, y, z); // 順番に入る
println(cout, "{2} + {1} = {0}", z, y, x); // 2番目、1番目、0番目の順に入る
置換フィールドにオプションを埋め込むことで、値の出力形式を細かく指定可能です。オプションは、従来のprintf関数におけるものを踏襲していますが、省略して既定のフォーマットで出力させることができます。
オプションは、位置パラメータと区別するために、コロン(:)で区切って指定します。オプションには、型、幅、フィル(埋め文字)、揃え方向(アラインメント)、符号、プレフィクス、精度などを以下の形式で指定できます。
[[フィル]アラインメント][符号][#][0][幅][.精度]L型
- フィル:揃えるときに使う埋め文字(既定値は半角スペース)
- アラインメント:揃え方向の指定(>:右揃え、<:左揃え、^:中央揃え)
- 符号:符号の指定(+:正数に符号を表示する、-:負数のみ符号を表示(既定)、スペース:正数にはスペースを表示)
- #:代替表現(0xなど形式がわかる表記)の指定
- 0:符号を考慮して0で埋める指定
- 幅:出力する幅(置換フィールドを使って変数で指定可)
- 精度:浮動小数点数では小数点以下の桁数、文字列では文字数(置換フィールドを使って変数で指定可)
- L:ロケールを考慮するか
- 型:値の表現方法
型には、以下の表に挙げるものをデータ型に応じて指定できます(?は、C++ 23から利用可能になったデバッグ出力の指定です)。
| 型 | 型指定 | 概要 |
| 文字列 | s(既定)または? | 文字列をそのまま、あるいは引用符で囲ってエスケープ処理を施して出力 |
| 文字 | c(既定)または? | 文字をそのまま、あるいは引用符で囲ってエスケープ処理を施して出力 |
| 論理値 | s(既定) | false/trueのいずれかで出力 |
| 整数 | b/B, d, o, x/X, c | 2進整数、10進整数、8進整数、16進整数、文字 |
| 浮動小数点数 | f/F, e/E, a/A, g/G(既定) | 指数表記なし、指数表記あり、16進数で指数表記、値によって指数表記 |
| ポインタ | p | アドレスを出力 |
以下は、主なオプションの使用例です。
auto s = "Hello\nWorld!";
auto c = '\t';
auto d = 12345678;
auto f = 3.14159;
void* p = &x;
println(cout, "String: {:s}", s); // String: Hello
// World!
println(cout, "String(debug): {:?}", s); // "Hello\nWorld!"
println(cout, "Character: {:c}", c); // (見えないがタブが出力)
println(cout, "Character(debug): {:?}", c); // '\t'
println(cout, "Integer: {:#032b}, {:010d}, {:020o}, {:#020x}", d, d, d, d);
// 0b000000101111000110000101001110, 0012345678, 00000000000057060516, 0x000000000000bc614e
println(cout, "Float: {:.2f}, {:e}, {:a}, {:g}", f, f, f, f);
// 3.14, 3.141590e+00, 1.921f9f01b866ep+1, 3.14159
println(cout, "Pointer: {:p}", p); // 0x7ff7b4220c74
