C++20の文字列フォーマットライブラリ std::format

C++20では、ついに新しい文字列フォーマットライブラリが導入されます。

* [P0645R10 Text Formatting](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0645r10.html)

今のC++標準ライブラリはCから引き継いだ`printf`系の関数群と、C++で導入された`iostream`という2つのフォーマットライブラリを持っています。

`printf`系関数には多くの問題があり、できればC++で使いたくはないものです[^1]。

[^1]: [SL.io.3: Prefer iostreams for I/O](http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#slio3-prefer-iostreams-for-io)

* 型安全ではない(Cの可変長引数そのものが)
* 書式ではなく、変数の型を指示する書式指定がたくさんある (`%d`と`%ld`など)
* 標準ではユーザー定義型へ拡張できない
* 書式文字列は実装による差が大きい

一方で`printf`スタイルには以下の利点もあり、C++より後発の言語でも書式文字列を使ったフォーマットは広く採用されています。

* 書式と引数がはっきり分かれている
* 普通の関数呼び出しとして理解可能
* 多くのプログラマーが慣れている

C++20のフォーマットライブラリ(`<format>`)は書式文字列の分かりやすさと`iostream`の拡張性・型安全性の両方を備えたライブラリであり、一言で言えば**現代版`printf`**です。

* C#, Python, Rustなどで実績がある、`{}`で引数を展開する新しい書式文字列
* フォーマットできる型、書式文字列の両方が拡張可能
* アライメントや2進数などフォーマット自体の機能も強化

```cpp
string message = format("The answer is {}.", 42);
```

`std::format`は規格化と並行して[{fmt}](https://github.com/fmtlib/fmt)ライブラリで実装が進められていて、すでに広く利用されています。

## 使い方

以下の4つのフォーマット関数と、それらの`wstring_view`を引数とするオーバーロードが提供されます。`u(8|16|32)string`への対応は無いようです。

この他に、可変長引数を型消去した内部実装用の関数も公開されていますが、通常は使う必要はありません。

### format

```cpp
template<class... Args>
string format(
  string_view fmt,
  const Args&... args);
```

書式文字列`fmt`を使って引数`args...`をフォーマットした文字列を返す。
`fmt`が書式文字列でない場合は `format_error` を投げる。

```cpp
string message = format("The answer is {}.", 42); // => "The answer is 42."
```

### format_to 

```cpp
template<class Out, class... Args>
Out format_to(
  Out out,
  string_view fmt,
  const Args&... args);
```

書式文字列`fmt`を使って引数`args...`をフォーマットし、出力イテレーター`out`に出力する。
`out + N`を返す(`N == formatted_size(fmt, args...)`)。
`fmt`が書式文字列でない場合は `format_error` を投げる。

```cpp
string buffer;
format_to(back_inserter(buffer), "The answer is {}.", 42);
cout << buffer; // The answer is 42.
```

### format_to_n

```cpp
template<class Out, class... Args>
format_to_n_result<Out> format_to_n(
  Out out,
  iter_difference_t<Out> n,
  string_view fmt,
  const Args&... args);

template<class Out>
struct format_to_n_result {
    Out out;
    iter_difference_t<Out> size;
};
```

書式文字列`fmt`を使って引数`args...`をフォーマットし、最大で`n`文字を出力イテレーター`out`に出力する。
`{ out + M, N }`を返す(`N == formatted_size(fmt, args...)`、`M == min(max(n, 0), N)`)。
`fmt`が書式文字列でない場合は `format_error` を投げる。

```cpp
char buffer[256];
auto [end, n] = format_to_n(buffer, size(buffer)-1, "The answer is {}.", 42);
*end = '\0'; // null文字は出力されない
cout << buffer; // The answer is 42.
```

### formatted_size

```cpp
template<class... Args>
size_t formatted_size(
  string_view fmt,
  const Args&... args);
```

書式文字列`fmt`を使って引数`args...`をフォーマットした文字列の長さを返す。
`fmt`が書式文字列でない場合は `format_error` を投げる。

# 書式
書式文字列中では、`{`と`}`で囲まれた範囲が置換フィールドとなります(エスケープシーケンスは`{{`と`}}`)。

置換フィールドの書式は次の通りです(`[]`は省略可の意味)。

```
{ [引数ID] [: オプション] }
```

* 引数IDは0から始まる番号で、何番目の引数で置換するかを指定します。
* 引数IDを一部のフィールドだけに書くことはできません(すべての置換フィールドに指定するか、すべての置換フィールドで指定しないかのどちらかのみ)。
* オプションの書式は引数の型によって異なります。

## 標準のオプション書式

組み込みの型に対して使える標準のオプション書式は次の通りです(`[]`は省略可の意味)
基本的にprintfの書式を踏襲しています。
オプションが無くても`<iostream>`と同じようにいい感じのデフォルト書式が使われます。

```
[[fill] align] [sign] ['#'] ['0'] [width] ['.' precision] [type]
```

* `fill` : アライメントに使う文字 (デフォルト: スペース)
* `align` : アライメント(デフォルトは型による)
    * `>` : 右寄せ
    * `<` : 左寄せ
    * `^` : 中央寄せ
    * `=` : 符号の後にアライメントをする
* `sign` : 符号
    * `+` : 正の数でも符号を表示する
    * `-` : 負の数の場合のみ符号を表示する(デフォルト)
    * ` ` : 正の数にはスペースを表示する
* `#` : 代替表現(`0x`など形式がわかる表記)を使う
* `0` : 符号を考慮して0で埋める (`fill`を`0`、`align`を`=`にした場合と同じ)
* `width` : 幅 (省略時は値に応じて幅が決まり、アライメントは機能しない)
    * 置換フィールドを使って変数で指定できる
* `precision` : 精度(浮動小数点数の場合)、使う文字数(文字列の場合)
    * 置換フィールドを使って変数で指定できる
* `type` : 値の表現方法(表を参照)

### 文字列型の場合

| type     | 意味       |
|:-----------|:-----------|
| s (省略可) | 文字列を`precision`文字だけ出力する |

### 文字型の場合

| type     | 意味       |
|:-----------|:-----------|
| c (省略可) | 1文字                                    |

### 整数型の場合

| type     | 意味       |
|:-----------|:-----------|
| b          | 2進数(小文字)                 |
| B          | 2進数(大文字)                 |
| o          | 8進数                          |
| d (省略可) | 10進数                         |
| n          | 10進数(ロケールを考慮する)      |
| x          | 16進数(小文字)                 |
| X          | 16進数(大文字)                 |

### 浮動小数点数型の場合

| type     | 意味       |
|:-----------|:-----------|
| f,F          | 指数表記しない             |
| e          | 指数表記(小文字)           |
| E          | 指数表記(大文字)           |
| a          | 16進指数表記(小文字)       |
| A          | 16進指数表記(大文字)       |
| g (省略可) | 値に応じて`f`または`e` |
| G          | 値に応じて`F`または`E` |
| n          | ロケールを考慮して値に応じて指数表記する |

### ポインターの場合

すべてのポインター型と`nullptr_t`(`nullptr`の型)が該当します。

| type     | 意味       |
|:-----------|:-----------|
| p (省略可) | アドレスを出力する (`uintptr_t`にキャストして小文字16進数で出力) |

## 引数の型変換

`charT`をフォーマッターの文字型とします。

* 書式文字列が`string_view`の場合は`char`、`wstring_view`の場合は`wchar_t`

format系関数の可変長引数に渡した値の1つを`const T& v`で受け取るとすれば、変換後の値は次の通りです。

* `T`が`bool`,`charT`,`charT*`,`double`,`long double`の場合はそのまま
* `T`が`char`で`charT`が`wchar_t`の場合は`static_cast<wchar_t>(v)`
* `T`が符号ありの整数型の場合
    * 大きさが`int`以下の場合は`static_cast<int>(v)`
    * 大きさが`long long int`以下の場合は`static_cast<long long int>(v)`
* `T`が符号なしの整数型の場合
    * 大きさが`unsigned int`以下の場合は`static_cast<unsigned int>(v)`
    * 大きさが`unsigned long long int`以下の場合は`static_cast<unsigned long long int>(v)`
* `T`が`float`の場合は`static_cast<double>(v)`
* `T`が`basic_string_view<charT>`に変換できる場合は`basic_string_view<charT>(v)`
* `T`が`nullptr_t`の場合は`static_cast<const void*>(nullptr)`
* `T`がポインターの場合は`static_cast<const void*>(v)`
* それ以外の場合は`v`のアドレスが保存され、ユーザー定義フォーマッターが使われる


## Tips

* ロケールを考慮しない場合、算術型の出力は[std::to_chars](https://cpprefjp.github.io/reference/charconv/to_chars.html)で行われます。

## 例(P0645R10より)

```cpp
char c = 120;
format("{:6}", 42);      // "    42"
format("{:6}", 'x');     // "x     "
format("{:*<6}", 'x');   // "x*****"
format("{:*>6}", 'x');   // "*****x"
format("{:*^6}", 'x');   // "**x***"
format("{:=6}", 'x');    // エラー
format("{:6d}", c);      // "   120"
format("{:=+06d}", c);   // "+00120"
format("{:0=#6x}", 0xa); // "0x000a"
format("{:6}", true);    // "true  "
```

```cpp
double inf = numeric_limits<double>::infinity();
double nan = numeric_limits<double>::quiet_NaN();
format("{0:} {0:+} {0:-} {0: }", 1);   // "1 +1 1  1"
format("{0:} {0:+} {0:-} {0: }", -1);  // "-1 -1 -1 -1"
format("{0:} {0:+} {0:-} {0: }", inf); // "inf +inf inf  inf"
format("{0:} {0:+} {0:-} {0: }", nan); // "nan +nan nan  nan"
```

```cpp
format("{}", 42);                      // "42"
format("{0:b} {0:d} {0:o} {0:x}", 42); // "101010 42 52 2a"
format("{0:#x} {0:#X}", 42);           // "0x2a 0X2A"
format("{:n}", 1234);                  // "1,234" (ロケールによる)
```

# 拡張性

書式文字列と使える型の両方が拡張可能です。

拡張するには、クラステンプレート`formatter`を特殊化します。
`ostream`との間に`operator<<`を定義することでストリームに対応できるのと同じように、`formatter`を特殊化すると自動的に使ってくれます。

```cpp
// std::tmに対する特殊化の例
template<>
struct formatter<tm> {
  constexpr format_parse_context::iterator parse(format_parse_context& ctx);

  template<class FormatContext>
  typename FormatContext::iterator format(const tm& tm, FormatContext& ctx);
};

time_t t = time(nullptr);
string date = format("The date is {0:%Y-%m-%d}.", *localtime(&t));
```

* `parse`関数は書式文字列のうち、対応するフィールドのフォーマット仕様(`{}`内の`:`から`}`までの部分)の解析を行います。なお、`parse`関数が`constexpr`である場合、書式文字列はコンパイル時に解析される可能性があります。
* `format`関数は値をフォーマットし、イテレーター(`ctx.begin()`)に出力します。

オプションの値などの情報はフォーマッターのメンバー変数に保存することができます。

次のコードは組み込みオプションの `width` を再現し、幅を変数で指定できるカスタムフォーマッターの実装例です。

```cpp
// P0645R10から引用
struct Answer {};

template<>
  struct formatter<Answer> {
    int width_arg_index = 0;

    // Parses dynamic width in the format "{<digit>}".
    auto parse(format_parse_context& parse_ctx) {
      auto iter = parse_ctx.begin();
      auto get_char = [&]() { return iter != parse_ctx.end() ? *iter : 0; };

      if (get_char() != '{')
        return iter;
      ++iter;
      char c = get_char();
      if (!std::isdigit(c) || (++iter, get_char()) != '}')
        throw format_error("invalid format");
      width_arg_index = c - '0';
      return ++iter;
    }

    auto format(Answer, format_context& format_ctx) {
      auto arg = format_ctx.arg(width_arg_index);
      int width = visit_format_arg([](auto value) -> int {
        if constexpr (!std::is_integral_v<decltype(value)>)
          throw format_error("width is not integral");
        else if (value < 0 || value > std::numeric_limits<int>::max())
          throw format_error("invalid width");
        else
          return value;
        }, arg);
      return format_to(format_ctx.out(), "{:{}}", 42, width);
    }
  };

std::string s = format("{0:{1}}", Answer(), 10);
// s == "        42"
```

ここで、`format_parse_context` はそれ自身が `char` のrangeになっています。

# パフォーマンス

P0645R10によれば`printf`、`ostringstream`、`to_string`より速いようです。

```
// P0645R10から引用
Run on (4 X 3100 MHz CPU s)
2018-01-27 07:12:00
Benchmark              Time           CPU Iterations
----------------------------------------------------
sprintf           882311 ns     881076 ns        781
ostringstream    2892035 ns    2888975 ns        242
to_string        1167422 ns    1166831 ns        610
format            675636 ns     674382 ns       1045
format_to         499376 ns     498996 ns       1263
```

速くなる要因としてはロケール依存の処理をしないことが考えられます。

また、コードサイズが増えすぎないように[thin-template](https://ja.wikibooks.org/wiki/More_C%2B%2B_Idioms/薄いテンプレート(Thin_Template))を使っていて、可変長引数の部分は型消去して別の構造体に詰めています。実際には`format_to`は次のように定義されます。

```cpp
template<class Out, class... Args>
Out format_to(Out out, string_view fmt, const Args&... args) {
  using context = basic_format_context<Out, decltype(fmt)::value_type>;
  return vformat_to(out, fmt, {make_format_args<context>(args...)});
}
```

# さらに詳しく

https://cpprefjp.github.io/reference/format.html

# 謝辞

この記事は[C++20を相談しながら調べる会 #3](https://cpp20survey.connpass.com/event/147002/)の結果として書かれました。