こんにちは。プログラマーのAです。トイロジックではゲーム開発を行っていて気づいたちょっとしたテクニックや、疑問に思って調べた情報などを、Tips という形で誰でも見られるよう共有しています。今回はその中から C++ に関するネタをいくつかご紹介します。

ただし、その特性上、どうしてもマニアックな内容になってしまいます。ちょっと難しく感じてしまうかもしれませんが、必須テクニックというわけではありませんので、気楽にお読みください。

if文とswitch文の新しい文法

for文は以下のような構文になっています。

for (初期化式; 条件式; 変化式) 文  

C++17 では、for文と同じように、if文とswitch文で初期化式と条件式を別々に記述できるようになりました。

if (int a = foo(); a != 0) {
    b = 100 / a;
}

ラムダ式のキャプチャあれこれ

ラムダ式の先頭の [ ] で変数をキャプチャすることで、外側の変数を使用することができます。
キャプチャにはさまざまな方法があるので、分かりやすいようにまとめてみました。

auto f1 = [x] { foo(x); };         // コピー
auto f2 = [&x] { x = 0; };         // 参照
auto f3 = [&x, y] { x = y; };      // 混在
auto f4 = [=] { foo(x, y, z); };   // 全部コピー
auto f5 = [&] { x = y = z = 0; };  // 全部参照
auto f6 = [=, &x] { x = y + z; };  // x は参照、ほかはコピー
auto f7 = [&, z] { x = y = z; };   // z はコピー、ほかは参照

少し複雑な書き方も紹介しておきます。

auto f8 = [a = x + 1] { foo(a); };          // 初期化キャプチャ (C++14)
auto f9 = [x = x] { foo(x); };              // [x] { foo(x); } と同じ
auto f10 = [x = std::move(x)] { foo(x); };  // ムーブキャプチャ

auto f11 = [this] { foo(member_); };  // this をキャプチャ
auto f12 = [=] { foo(member_); };     // デフォルトコピーキャプチャは this を含む
// 一見 member_ をコピーキャプチャしてるようだが、キャプチャしてるのは this のみなので注意!

算術型の安全な変換

算術型をキャストすると、値が変わってしまうことがあります。

uint8_t i = uint8_t(256);  // たぶん 0 になる

これは分かりづらいバグに繋がってしまう可能性があります。
そこで、波括弧 { } によるキャストを使うと、情報が失われる場合にコンパイルエラーになってくれます。

uint16_t i = 0;
 
uint8_t{i};    // NG
int16_t{i};    // NG
int32_t{i};    // OK
float{i};      // NG
bool{i};       // NG
int32_t{0.f};  // NG
double{0.f};   // OK

【enum】基底の整数型

ここからは enum 関連のネタを3つご紹介します。
enum を定義する際には基底の整数型を指定することができます。

enum Alpha : int { A = 0 };
enum class Bravo : int { B = 0 };

この指定は省略することもできます。
enum class に基底の整数型を指定しない場合、int になります。

enum class Alpha { A = 0 };                                     // int
enum class Bravo { B = 0xFFFFFFFFFFFFFFFF };                    // エラー!表現できない値
enum class Charlie : std::uint64_t { C = 0xFFFFFFFFFFFFFFFF };  // OK

enum に基底の整数型を指定しない場合、どの整数型になるかは実装定義になります。
ただし、表現できる値であれば int より大きいサイズの型にはなりません。

enum Alpha { A = 0 };                   // int 以下の整数型
enum Bravo { B = 0xFFFFFFFFFFFFFFFF };  // たぶん std::uint64_t

【enum】基底型を取得する

std::underlying_type を使うと enum の基底型を取得することができます。

using type = std::underlying_type_t<Alpha>  // int

【enum】初期化リストによる変換

C++17 では { } で enum class を初期化できます(C++14 までは static_cast を使う必要があります)。
キャストと異なり、精度が足りない場合はエラーを出してくれます(上記の「算術型の安全な変換」をご参照ください)。

enum class Delta : std::int8_t {};
Delta a{0};             // C++17:OK, C++14:エラー!enum class を数値で初期化できない
Delta b = Delta{0};     // 上と同じ
Delta c = Delta{1000};  // エラー! int8_t で表現できない

最後に

いかがだったでしょうか。私は周りから質問された内容を Tips としてまとめることが多いです。その際に正確な用語などを調べたりすることで、自分自身の理解も深まったり、曖昧だった知識が整理されることが少なくありません。やはりひとりでやっていては気づけないことがたくさんあると感じます。
いっしょにスキルアップを目指したい!というみなさんのエントリーをお待ちしております!