C++20 の std::format

今日は、C++20 の新しいフォーマット ライブラリ std::format に関する Peter Gottschling のゲスト投稿を喜んでお届けします。 . std::format に感謝 、テキストの書式設定は C++20 で Python と同じくらい簡単になります。

Peter は、プロの C++ 開発者向けの必読本「Discovering Modern C++」の著者です。

新しいフォーマット

従来のストリーム形式では、かなりの量の入力が必要です。 printf のフォーマット文字列 などは明らかにより表現力があり、以前に複数の I/O マニピュレータで記述したものを、少数のシンボルで宣言できます。

それでも、printf は使用しないことをお勧めします。 .理由は 2 つあります。ユーザー タイプでは使用できないことと、タイプ セーフではないことです。フォーマット文字列は実行時に解析され、次の引数はあいまいなマクロ メカニズムで処理されます。引数がフォーマット文字列と一致しない場合、動作は未定義であり、プログラムがクラッシュする可能性があります。たとえば、文字列がポインターとして渡され、ポイントされたアドレスからバイトが読み取られ、char として出力されます。 バイナリ 0 まで メモリに見つかります。誤って int を印刷しようとした場合 文字列として、int 値は、char のシーケンスからのアドレスとして誤って解釈されます 印刷されます。これにより、まったく無意味な出力が発生するか、アドレスにアクセスできない場合は (可能性が高い) メモリ エラーが発生します。最近のコンパイラは (コンパイル時にわかっている場合) フォーマット文字列を解析し、引数の不一致について警告することを認めなければなりません。

新しいフォーマット ライブラリ {執筆時点では、このライブラリをサポートするコンパイラはなく、例はそのプロトタイプ バージョンである fmt ライブラリで実装されていました。フォーマット ライブラリは、フォーマット文字列の表現可能性をタイプ セーフおよびストリーム I/O のユーザー拡張可能性と組み合わせて、出力内の引数を並べ替える機会を追加します。

積分

正式な仕様の代わりに、いくつかの printf を移植します cppreference.com から新しい形式への例:

print("Decimal:\t{} {} {:06} {} {:0} {:+} {:d}\n", 1, 2, 3, 0, 0, 4, -1);

print("Hexadecimal:\t{:x} {:x} {:X} {:#x}\n", 5, 10, 10, 6);

print("Octal:\t\t{:o} {:#o} {:#o}\n", 10, 10, 4);

print("Binary:\t\t{:b} {:#b} {:#b}\n", 10, 10, 4);

このスニペットは以下を出力します:

Decimal: 1 2 000003 0 0 +4 -1

Hexadecimal: 5 a A 0x6

Octal: 12 012 04

Binary: 1010 0b1010 0b100

最初の 2 つの数値は、形式情報を指定せずに印刷されただけです。フォーマット指定子 :d. で 10 進数を要求すると、同じ出力が生成されます。 3 番目の数字は、(最小) 6 文字幅で印刷され、0 で埋められます。 秒。指定子 + すべての数字の符号を強制的に出力できます。 printf unsigned の指定が可能 数値の出力。出力する値が負の場合、これは誤った大きな数値につながります。 format ライブラリは unsigned のユーザー宣言を控えています この情報は、引数に従って の型に既に含まれているため、出力されません。誰かが負の値をほぼ正の値として出力したいという衝動を感じた場合、明示的に変換する必要があります。

2 行目は、値を 16 進数で出力できることを示しています。9 より大きい数字は大文字と小文字の両方で表示されます。指定子 # プレフィックス 0x を生成します 16 進リテラルで使用されます。

同様に、オプションでリテラル接頭辞に従って、値を 8 進数およびバイナリとして出力できます。

浮動小数点数

浮動小数点数を使用すると、より多くの書式設定オプションがあります:

print("Default:\t{} {:g} {:g}\n", 1.5, 1.5, 1e20);

print("Rounding:\t{:f} {:.0f} {:.22f}\n", 1.5, 1.5, 1.3);

print("Padding:\t{:05.2f} {:.2f} {:5.2f}\n", 1.5, 1.5, 1.5);

print("Scientific:\t{:E} {:e}\n", 1.5, 1.5);

print("Hexadecimal:\t{:a} {:A}\n\n", 1.5, 1.3);

次に取得します:

Default: 1.5 1.5 1e+20

Rounding: 1.500000 2 1.3000000000000000444089

Padding: 01.50 1.50 1.50

Scientific: 1.500000E+00 1.500000e+00

Hexadecimal: 0x1.8p+0 0X1.4CCCCCCCCCCCDP+0

中かっこを空にするか、コロンのみを含めると、デフォルトの出力が得られます。これは、フォーマット指定子  :g に対応します。 マニピュレータなしのストリームと同じ出力が生成されます。小数桁数は、ドットと書式指定子 f の間で指定できます .次に、値はその精度に丸められます。要求された数値が値の型で表現できるものよりも大きい場合、最後の桁はあまり意味がありません。ドットの前の数字は、出力の (最小) 幅を指定します。整数と同様に、先頭の 0 をリクエストできます 秒。浮動小数点数は、大文字または小文字の科学表記法で出力できます e 指数部分を開始します。 16 進出力を使用して、正確に同じビットで別のプログラムの変数を初期化できます。

出力のリダイレクト

出力は他の std::ostream ( にリダイレクトできます ostream.h を含める必要があります fmt ライブラリを使用します。):

print(std::cerr, "System error code = {}\n", 7);

ofstream error_file("error_file.txt");

print(error_file, "System error code = {}\n", 7);

引数の並べ替えと名前付け

printf とは対照的 、引数を並べ替えることができるようになりました:

print("I'd rather be {1} than {0}.\n", "right", "happy");

引数を位置で参照するだけでなく、引数に名前を付けることができます:

print("Hello, {name}! The answer is {number}. Goodbye, {name}.\n",
 arg("name", name), arg("number", number));

またはもっと簡潔に:

print("Hello, {name}! The answer is {number}. Goodbye, {name}.\n",
 "name"_a=name, "number"_a=number);

  • Rainer:C++20 には名前付き引数がありません。

この例は、引数を複数回出力できることも示しています。

多言語ソフトウェアでは、自然な言い回しを提供するために、引数の並べ替えが非常に重要です。

ここで、2 つの値の平均を 5 つの言語で出力します。

void print_average(float v1, float v2, int language)

{ 

 using namespace fmt;

 string formats[]= {"The average of {v1} and {v2} is {result}.\n",

 "{result:.6f} ist der Durchschnitt von {v1} und {v2}.\n",

 "La moyenne de {v1} et {v2} est {result}.\n",

 "El promedio de {v1} y {v2} es {result}.\n",

 "{result} corrisponde alla media di {v1} e {v2}.\n"};

 print (formats[language], "v1"_a= v1, "v2"_a= v2, "result"_a= (v1+v2)/2.0f);

} 

もちろん、ドイツ語版は最も衒学的なものであり、何があっても 6 桁の 10 進数を要求します:

The average of 3.5 and 7.3 is 5.4.

5.400000 ist der Durchschnitt von 3.5 und 7.3.

La moyenne de 3.5 et 7.3 est 5.4.

El promedio de 3.5 y 7.3 es 5.4.

5.4 corrisponde alla media di 3.5 e 7.3.

確かに、この例は引数を並べ替えなくても機能しますが、テキストと書式設定を値から分離する重要な可能性をうまく示しています。書式設定されたテキストを文字列に格納するために stringstream は必要ありません 関数形式で直接実行できます。

次は?

次の投稿では、Peter が std::format の紹介を続けます。 .彼はユーザー定義の書式設定について書いています。