📝

【C++】複雑な初期化をする時でも変数に const を付与する

2023/10/18に公開

C++ でも Rust のようにブロックを式として扱いたい・・・!

// x == 4
let x = {
    let a = 2;
    a * a
}

複雑な初期化が必要な時に const を付与できない

C++ では変数を宣言する際に、少し複雑な初期化が必要なケースで const が付与できずに悲しい気持ちになることが多々あります。

/* 本当は const が付けたい */
std::string code;

switch(color) {
case Color::RED:
  code = "#ff0000";
  break;
case Color::GREEN:
  code = "#00ff00";
  break;
case Color::BLUE:
  code = "#0000ff";
  break;
}

今回のケースであれば、ギリギリ三項演算子を使うことで const を付与することもできます。
とは言え、このスタイルは一歩間違えれば黒魔術に成り果てる匂いがぷんぷんします。

const std::string code = color == Color::RED ? "#ff0000" :
                         color == Color::GREEN ? "#00ff00" :
			 color == Color::BLUE ? "#00ff00" : "";

IIFE とは

IIFE は Immediately Invoked Function Expression の略で、即時実行関数のことです。
javascript で重宝されるイディオムで次のように使います。

(() => {
    // ここにコードを書く
})();

C++ ではラムダ式を利用して同様のことが行えます。

[] {
  // ここにコードを書く
}();

当然式なので、値を返すことができます。

const int num = [] {
  return 0;
}();

IIFE を使った初期化

冒頭のコードを IIFE を使った初期化に書き換えると const を付与することが出来ます。
const を付与できるだけでなく、初期化に利用する一時変数をローカルスコープに押し込めたり、早期リターンを活用できるなど、いくつかのメリットを享受できます。

この手法は C++ Core Guidelines でも推奨されています。
Use lambdas for complex initialization, especially of const variables

もちろんこのロジックが広く使われる場合、 Factory Method として切り出してあげるのがベストプラクティスです。
関数ローカルで一度だけ使用したいようなロジックで関数に切り出すこと自体が冗長な場合に、このような記法が力を発揮します。

const std::string code = [&] {
  switch(color) {
  case Color::RED:
    return "#ff0000";
  case Color::GREEN:
    return "#00ff00";
  case Color::BLUE:
    return "#0000ff";
  default:
    return "";
  }
}();

おわりに

容量用法を守れば非常に強力なテクニックになります。

参考文献

https://rigtorp.se/iife/

https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#es28-use-lambdas-for-complex-initialization-especially-of-const-variables

Discussion