C++と色々

主にC++やプログラムに関する記事を投稿します。

C++20の `std::ssize` が便利

C++20には std::ssize という関数が追加されています。

cpprefjp.github.io

この関数がどう便利なのか紹介します。

C++ではranged based forが存在しますが、インデックス付きでコンテナをイテレートしたい時は通常のfor文を使う場合もあります。

C++20でranged based forにも初期化式を書くことができるようになりましたが、continueと相性が悪いなど完全にfor文を置き換えられるかと言うとそういうわけではありません。

// C++20初期化式付きのrange based for
for (int i = 0; auto&& elem : container) {
  // elemとiを使った処理
  // containerがstd::vectorなどであれば、container[i] == elemになる
  ++i;
}

既存の問題点

標準コンテナを通常のfor文でイテレートするとき、インデックス変数を符号付き整数型にすると、for文の終了条件式を書くときに、インデックス変数と標準コンテナの size() 関数で符号が一致しません。

// i < container.size()は符号付き整数型と符号なし整数型の比較になる
for (int i = 0; i < container.size(); ++i) {
  ...
}

コンパイラの警告を適切に設定してる場合、コンパイラの警告が表示されます*1。警告を消すだけならインデックス変数を符号なし整数型にする方法もあります。

// 型を明示
for (unsgined i = 0; i < container.size(); ++i)
// 符号なし整数リテラルで推論させる
for (auto i = 0U; i < container.size(); ++i)
// size()の型に揃える
for (decltype(container.size()) i = 0; i < container.size(); ++i)

また、条件式の符号が不一致しているコードはオーバーフローによる不具合の原因にもなりえます。

// インデックス付きでイテレートしたい時に
// 全要素ではなく0番目や最後の要素はスキップしたいケースなどは往々にしてある
// container.size()が0だったら、めちゃくちゃループする
for (int i = 0; i < container.size() - 1; ++i) {
  ...
}

解決方法

size() を符号付き整数型にキャストすることで上記の問題は解決します。

for (int i = 0; i < static_cast<int>(container.size()) - 1; ++i) {
  ...
}

std::ssize を使えば static_cast を使うよりも短くかつ適切な符号付き整数型にキャストしてくれます。

// container.size()が0でもstd::ssize(container) - 1の結果は-1となり、
// forは実行されずに終わる
for (int i = 0; i < std::ssize(container) - 1; ++i) {
  ...
}

というわけでコンテナのサイズ取得には std::ssize を使っていきましょう。もちろん配列にも対応しています。

(本音を言うとRageライブラリとインデック付きのRangeジェネレーターが標準に入ってくれるのが一番嬉しいです)

*1:MSVCならC4388、g++なら-Wsign-compare