C++20:ユーザー定義型の std::format を拡張する

Peter Gottschling は、彼の最後の投稿「C++20 の std::format」で、C++20 の新しい書式設定ライブラリの基本を紹介しました。今日の投稿では、Peter がユーザー定義型のフォーマットについて書いています。

テンプレートの特殊化の最初の例は、ユーザー タイプをサポートするために導入された新しいフォーマット ライブラリのカスタマイズです。

ユーザー定義型の書式設定

たとえば、 dmc::vector を選択します (dmc は、著者による本「Discovering Modern C++」の名前空間です) 単一の値の書式設定を指定したいクラスです。さらに、フォーマット文字列に文字 'c' が含まれている場合、囲み括弧を中括弧に置き換えたいと考えています。 .この目的のために、クラス std::formatter を特殊化する必要があります。 (または fmt::formatter プロトタイプ ライブラリ fmt の場合 )。私たちの特殊化には、メソッド parse が含まれます と format .

前者から始めましょう:

template <typename Value>
struct formatter<dmc::vector<Value>>
{
 constexpr auto parse(format_parse_context& ctx)
 {
 value_format= "{:"; 
 for (auto it= begin(ctx); it != end(ctx); ++it) {
 char c= *it;
 if (c == 'c')
 curly= true;
 else
 value_format+= c;
 if (c == '}')
 return it;
 }
 return end(ctx);
 }
 // ...
 bool curly{false};
 std::string value_format;
};

引数として、begin の構文解析コンテキストが与えられます。 iterator は、フォーマット指定の最初の文字を指します。つまり、~コロンの後の最初の文字であり、コロンがない場合は左中括弧の後の最初の文字です。フォーマット仕様をローカルの value_format, とほぼ同じようにコピーします 特殊文字 'c' のみ スキップされます。簡単にするために、次の右中括弧がフォーマット文字列を終了するように、形式には左中括弧または右中括弧が含まれていないと仮定します。最後に、右中括弧または終了イテレータを指すイテレータを返します。

この情報を使用して、vector を出力できます。 メソッド format で :

template <typename Value>
struct formatter<dmc::vector<Value>>
{
 template <typename FormatContext>
 auto format(const dmc::vector<Value>& v, FormatContext& ctx)
 {
 auto&& out= ctx.out();
 format_to(out, curly ? "{{" : "[");
 if (v.size() > 0)
 format_to(out, value_format, v[0]);
 for (int i= 1; i < v.size(); ++i)
 format_to(out, ", " + value_format, v[i]);
 return format_to(out, curly ? "}}" : "]");
 }
 // ...
};

まず、出力バッファへの参照を取得します。次に、開始ブレースまたはブラケットをそれに書き込みます。 format ではブレースには特別な意味があるため、 ライブラリでは、二重中括弧のエスケープ シーケンスが必要です。残りの出力は ostream と同等です 出力。最後に、出力バッファを返します。

これで、さまざまな形式を試すことができます:

dmc::vector<double> v{1.394, 1e9, 1.0/3.0, 1e-20};
print("v with empty format = {:}.\n", v);
print("v with f = {:f}.\n", v);
print("v curly with f = {:fc}.\n", v);
print("v width 9, 4 digits = {:9.4f}.\n", v);
print("v scient. = {:ec}.\n", v);

出力に応じて表示されます:

v with empty format = [1.394, 1000000000.0, 0.3333333333333333, 1e-20].
v with f = [1.394000, 1000000000.000000, 0.333333, 0.000000].
v curly with f = {1.394000, 1000000000.000000, 0.333333, 0.000000}.
v width 9, 4 digits = [ 1.3940, 1000000000.0000, 0.3333, 0.0000].
v scient. = {1.394000e+00, 1.000000e+09, 3.333333e-01, 1.000000e-20}.

全体として、新しいフォーマットは次のとおりです:

  • コンパクト :上記の例で示されています
  • 適応性: さまざまな出力オーダーへ
  • タイプセーフ :引数が一致しない場合、例外がスローされます
  • 拡張可能 :ユーザー定義型に拡張できます

これらの理由から、これは前述の手法よりも優れているため、十分なコンパイラ サポートが利用可能になったらすぐに使用することを強くお勧めします。

std::format を簡潔に紹介してくれた Peter Gottschling にもう一度感謝します。 .フォーマッティング ライブラリの紹介を完成させるために、いくつかの単語を追加させてください。

試してみる

Peter が既に述べたように、GitHub は fmt をホストしていました。 library は、C++20 の新しい書式設定ライブラリのプロトタイプです。 fmt のフロント ページ プロジェクトには、いくつかの簡単な例とパフォーマンスの数値が含まれています。これらの例には、例を実行するためのコンパイラ エクスプローラへの直接リンクが含まれています。

新しいフォーマット ライブラリのおかげで、chrono の期間を表示できます。 ライブラリ:

#include <fmt/chrono.h>

int main() {
 using namespace std::literals::chrono_literals;
 fmt::print("Default format: {} {}\n", 42s, 100ms);
 fmt::print("strftime-like format: {:%H:%M:%S}\n", 3h + 15min + 30s);
}

コンパイラ エクスプローラーでプログラムを実行すると、次の出力が得られます。

C++20 への移植

fmt から以前のプログラムを移植 C++20 フォーマット ライブラリは簡単です。 C++ 標準ヘッダー chrono を使用する必要があります と iostream .さらに、呼び出し fmt::print を置き換えます 関数 std::format で 結果を std::cout にプッシュします . std::format 指定されたフォーマット文字列とオプションのローカルに従って文字列を返します。

// formatChrono.cpp

#include <chrono>
#include <iostream>

int main() {
 using namespace std::literals::chrono_literals;
 std::cout << std::format("Default format: {} {}\n", 42s, 100ms) << "\n";
 std::cout << std::format("strftime-like format: {:%H:%M:%S}\n", 3h + 15min + 30s) << "\n";
}

次は?

次回の投稿では、引き続き便利な機能について説明します。 C++20 では、2 つの値の中点を計算できます。std::string かどうかを確認してください。 部分文字列で開始または終了し、 std::bind_front で callable を作成します .