std::printについて

# std::printとは

本記事はAdvent Calendar 2024の16日目である。

前記事: [TauriにC++を組み込んでGUIアプリケーションを作ろう](https://qiita.com/kizul/items/33c5b0db42a3475c7109)

```std::print``` とは、C++23で新たに追加された標準ライブラリの関数である。
今までのC++といえば、以下のように```ostream```のエイリアスである```cout```にシフト演算子で文字列を流し込むものであった。

```C++:いつものhelloworld.cpp
#include <iostream>

int main() {
    std::cout << "Hello world!" << std::endl;
}

```

親の顔より良く見るC++のはろわであるが、C++のはろわがどういった機能を使用して記述されているのかを説明するだけでC++の初学者が去るのに十分すぎる。
誰が学び始めに、「```cout```が```ostream```クラスのエイリアスとか、```ostream```はストリームバッファの出力ストリームを制御するとか、シフト演算子をオーバーライドして```cout```は投げられた値を処理する」という難解さに触れようとするだろうか。まだ変数や演算子の理解もない状態からその応用をこれでもかと押し付けてくるのは不親切でしかない。
実際、多くのメジャーなプログラミング言語たちがそれほど苦しまずに言語機能を知る最序盤で触れる関数によってはろわを実現していることを鑑みても、この記述は明らかに異質なのである。
もっとも、C++に慣れるということはこの関門を突破できるか否かにあるため、むしろこれはふるいにかけている、という要素もあるのだろうか。

そんな中にあって、C++の出力には明確なフォーマッティング処理がなかったため、C++20で`std::format`が実装された。それに伴い、より標準I/Oに寄り添った入出力方法が模索されるようになる。その中で生まれたのが`std::print`である。
`std::print`は他のメジャーなプログラミング言語群と比較して名付けられたAPI名であり、また本実装がC++に標準化されるまでの間format機能を拡張してきたfmtライブラリにて6年使用されてきたものである。

`std::print`で記述するもっとも簡単なはろわは以下の通り。

```C++:printではろわ.cpp
#include <print>

int main() {
    std::print("Hello world!");
}

```

## std::printの機能

`std::print`は`printf`ライクなフォーマッティングを行って出力することができる。

```C++:printでふぉーまっと入れてはろわ.cpp
#include <print>

int main() {
    std::string str = "World!";
    std::print("Hello {}", str); // "Hello World!"
}

```

また、CライクなFILEポインタに対しても、ストリームバッファに対してもprintすることができる。
なおストリームバッファに対して操作する場合はヘッダを`<print>`ではなく`<ostream>`にする必要がある。

```C++:FILEポインタに出力.cpp
#include <print>
#include <cstdio>

int main(){
    FILE* file = std::fopen("./hello.txt", "r+");
    std::print(file,"{}", "Hello World");
    fclose(file);
}

```

```C++:ファイルストリームに出力.cpp
#include <ostream>
#include <fstream>

int main(){
    std::fstream fs;
    fs.open("./hello.txt");
    std::print(fs,"{}", "Hello World");
    fs.close();
}

```

`std::print`は投げ込まれる文字列がunicodeの場合に自動的にunicodeとして出力してくれるが、C++20で実装されたu8string型の文字列を出力する場合はイテレータを利用してstring型に直す必要がある。

```C++:u8string.cpp
#include <print>
#include <string>

int main(){
    std::u8string u8str = u8"はろーわーるど!";
    std::print("{}", std::string(u8str.begin(), u8str.end()));
}

```

## ぱふぉーまんすてすと

[google-benchmark](https://github.com/google/benchmark)を使用し、以下の環境でテストした。

CPU: ryzen 3900X
OS: Windows on WSL in opensuse-tumbleweed

```shell:google-bench
Run on (24 X 3800.02 MHz CPU s)
CPU Caches:
  L1 Data 32 KiB (x12)
  L1 Instruction 32 KiB (x12)
  L2 Unified 512 KiB (x12)
  L3 Unified 16384 KiB (x1)
-----------------------------------------------------
Benchmark           Time             CPU   Iterations
-----------------------------------------------------
printf           5073 ns         5073 ns       113006
ostream          5256 ns         1173 ns       616476
print            5633 ns         5633 ns       125564

```
以下がテストしたサンプルコードである。

```c++:sample.cpp
#include <cstdio>
#include <iostream>
#include <print>

#include <benchmark/benchmark.h>

void printf(benchmark::State& s) {
  while (s.KeepRunning())
    std::printf("The answer is %d.\n", 42);
}
BENCHMARK(printf);

void ostream(benchmark::State& s) {
  while (s.KeepRunning())
  std::ios::sync_with_stdio(false);
  std::cout << "The answer is " << 42 << ".\n";
}
BENCHMARK(ostream);

void print(benchmark::State& s) {
  while(s.KeepRunning())
    std::print("The answer is {}.\n", 42);
}
BENCHMARK(print);

BENCHMARK_MAIN();
```

単純なパフォーマンス比較で考えるとどうしてもストリームバッファのオプションを使用できるcoutに軍配が上がってしまうが、printはまだ効率的な実装が行われている環境がないのでやむを得ないのかもしれない。
テストについてはオレオレ環境であったり、標準出力の/dev/null廃棄がうまく機能しないので--benchmark-filterオプションでごまかしたりなどしている部分があるためなんとも言えないのだが、より効果的なテストができたら更新・追記などしたい。

## しめ

std::printはまだ実装予定の機能がC++の国際標準化委員会の中で議論されている発展途上の機能である。
実行時のフォーマッティングはC++26であるし、今後決定されればより効率的な実装も望めるものである。
また、std::printという表記方法やフォーマッティングの方法が他言語との兼ね合いで実装内容を調整しているところを見ても、C++は古くからある言語ながらもいまだに発展していることを如実に実感できる。おそらく一生付き合っていっても枯れず廃れず電子世界の中で生き生きと成長しつづけるのではないだろうか。

## 参考
[P2093R14 Formatted output](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2093r14.html)
[cpprefjp std::print](https://cpprefjp.github.io/reference/print/print.html)


## おまけの追記

この記事を作るのにCopilotさんやGrokさんにはお世話になった。
AIに助けられるというなかなか良い時代になった。