32
28

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

C++20期待の新機能std::formatを試してみる

Last updated at Posted at 2021-05-30

ついにVisual Studio 2019(ver 16.10)でC++20の新機能、std::formatが使えるようになったということなので、試してみました。結論から言うと非常に満足いくものであり、sprintfはもう使わなくてもいいかな、という気持ちです。

前置き

C++で書式つき文字列を扱う場合、printfsprintfといったC言語の関数か、あるいはC++のiostreamを使うという2通りのやり方があります。

printfの場合、引数の数や型をチェックしてくれないという点やstd::stringと親和性がない問題が、そしてiostreamの場合は使い方が独特(<<, >>といった演算子、内部状態を持つ)という問題があります。

printfの手軽さと、iostreamの型安全性・拡張性を兼ね備えたboost::formatのようなライブラリも登場しましたが、やはり標準ライブラリに入っていないと導入が大変でした(最近ようやくC++界隈でもパッケージマネージャが広まってきましたが)。

そんなわけで、C++20にて新しい書式つき文字列のためのライブラリformatが導入されると聞き、私のように期待していたC++開発者も多いのではないでしょうか。

そして、2021年5月末、ようやくMicrosoftがVisual Studio 2019(16.10)にてformatの実装をリリースしました。GNU(libstdc++)やClang(libcxx)より先に出してくるとは思っていませんでした。近年のMicrosoftは本当にすごい。

Visual Studio 2019 バージョン16.10 のリリースノート
libstdc++のC++20開発状況 -> P0645をページ内検索
libcxxのC++20開発状況 -> P0645をページ内検索

導入

特に難しいことは何もないので、詳しくは解説しません。

  1. Visual Studio 2019のアップデート

    • Visual Studio 2019を持っていない方
    • Visual Studio 2019を持っている方
      • Visual Studio Installerを実行し、2019を最新にアップデートします。
      • その後Visual Studio 2019を起動し、バージョンが16.10.0以降になっていればOK
  2. C++のプロジェクトを作成

    • プロジェクトの種類はなんでもいいです(私は「コンソールアプリ」で試しています)
  3. C++の機能スイッチを/std:c++latestに設定する

    • プロジェクトの「プロパティ」を開く
    • 左側から「全般」を選択
    • 「C++ 言語標準」を「プレビュー - 最新の C++ Working Draft からの機能 (/std:c++latest)」に設定
    • ※ソリューション構成(Release/Debug)とプラットフォーム(x86/x64)を間違えないように注意

本来だと/std:c++latestではなく、/std:c++20の機能スイッチが提供される予定でしたが、バイナリレベルでの仕様(ABI)がまだ安定していないということで、実装されていません。/std:c++latestはぶっちゃけ人柱用の設定であり、ビルドしたバイナリの互換性はないことに注意してください。

基本

ライブラリformatをインクルードするだけで使用できます。

#include <iostream>
#include <format>

int main()
{
	using namespace std;
	cout << format("Hello {}!", "std::format") << endl;
}
Hello std::format!

使い方

std::formatによる書式のスタイルは、今まで使用してきたprintfiostreamとも異なる表記を用います。C#System.Console.WriteLinePythonf-stringsRustformatに近しいもので、これらを使ったことがある人ならすぐに使いこなすことができるでしょう。

書式文字列の中に変数を差し込みたい場合、カーリーブラケット{}を使用してその場所を指定します。

詳しい使い方についてはすでにQiitaに記事があるので、そちらを参照してください。

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

例外

以下では上記の記事では触れていないポイントについて簡単に解説していきます。

std::formatでは書式や引数が不正な場合、実行時例外としてstd::format_errorを発生させます。

try {
	// 1個しか引数がないのに、2個目を使用しようとしてみる
	cout << format("{1}", 0) << endl;
}
catch (const format_error& e)
{
	cout << e.what() << endl;
}

Argument not found.

書式文字列が不正な場合も例外が発生します。

format("{$324i0-g}", 0)
Invalid format string.

書式内で指定した型と引数の型が違う場合も例外が発生します。

format("{:d}", 4.0f)
invalid floating point type
format("{:f}", 42)
invalid integral type

fill指定に{}を使うとエラーになります(これ以外の文字は使える)。

format("{:{>10}", 42)
invalid fill character '{'
format("{:}>10}", 42)
Unmatched '}' in format string.

浮動小数点数以外でprecision指定はできません。

format("{:6.3}", 42)
Precision not allowed for this argument type.

ワイド文字列

もちろんwchar_twstringに対応しているので、日本語も扱えます。

#include <iostream>
#include <format>
#include <locale>

int main()
{
	using namespace std;
	setlocale(LC_CTYPE, "");
	wcout << format(L"{}", L"こんにちは!") << endl;
}
こんにちは!

fill指定にもワイド文字は使えますが、期待した動作にはならないようです。

wcout << format(L"{:あ^20}", L"中央寄せ") << endl;
ああああああ中央寄せああああああ  // 'あ'が幅1として扱われている?(2*4+12=20)

まあ、個人的には正直C/C++で日本語は扱いたくないなあと思っているのでどうでもいい気も……。

書式指定チートシートもどき

説明 記述 結果
1個 format("{}", 1) 1
2個 format("{} {}", 1, 2) 1 2
3個 format("{} {} {}", 1, 2, 3) 1 2 3
順序指定 format("{2} {1} {0}", 1, 2, 3) 3 2 1
文字(cは省略可) format("{:c}", 'C') C
文字列(sは省略可) format("{:s}", "C++") C++
bool format("{}", true) true
bool format("{}", false) false
整数(10進数,dは省略可) format("{:d}", 42) 42
整数(8進数) format("{:o}", 042) 42
整数(16進数) format("{:x}", 0xab) ab
整数(16進数) 大文字 format("{:X}", 0xab) AB
整数(16進数) 0x付き format("{:#x}", 0xab) 0xab
整数(16進数) 0X付き format("{:#X}", 0xab) 0XAB
整数(2進数) format("{:b}", 0b10101010) 10101010
整数(2進数) 0b付き format("{:#b}", 0b10101010) 0b10101010
整数(2進数) 0B付き format("{:#B}", 0b10101010) 0B10101010
整数(正数に+) format("{:+d}", 42) +42
浮動小数点数 format("{:f}", 123.456789) 123.456789
精度指定 format("{:6.3f}", 123.456789) 123.457
指数表記(e) format("{:e}", 123.456789) 1.234568e+02
指数表記(E) format("{:E}", 123.456789) 1.234568E+02
最適な表記を自動判定
(gは省略可)
format("{:g}", 123.456789) 123.457
左寄せ format("{:<12}", "left") left        
右寄せ format("{:>12}", "right")        right
中央寄せ format("{:^12}", "center")    center   
埋める文字を指定する format("{:!^12}", "hello") !!!hello!!!!

参考文献

Text Formatting - www.open-std.org: C++標準化委員会による仕様
format - cpprefjp - C++日本語リファレンス: 広範かつ詳細な解説

あとがき

これでもう以下のプログラムみたいな悲惨なコードは書かなくても済みそうですね。

char buffer[1000];
sprintf(buffer, "%d %d %d", a, b, d);
std::string str = buffer;

あとはVisual Studioでprintfを使うとコンパイル時に書式をチェックしてくれる機能をformatに対しても付けて欲しい。

さらに贅沢を言えば、メジャーなコンパイラ環境のどこでも使えるようになってくれたら……

32
28
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
32
28

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?