🔖

C++17で拡張された集成体初期化はstd::is_constructibleでない

2023/08/24に公開

概要

継承を含む集成体は、以下の問題を含んでいます。

  • 「C++17で拡張された集成体初期化」がstd::is_constructibleでない
  • C++20で許可された丸括弧での初期化を「C++17で拡張された集成体初期化」の記法で書けない

順に紹介していきます。

「C++17で拡張された集成体初期化」がstd::is_constructibleでない

「C++17で拡張された集成体初期化」とは、継承を含む集成体において以下のように初期化することを言います。

struct case_a_base {
  int a, b;
};

// 継承を含む集成体
struct case_a : case_a_base {
  float c;
};

// 普通の集成体
struct case_b { 
  int a, b;
  float c;
};

static_assert(case_a{1, 2, 3.0f}.c == 3.0f);   // C++17以降に許可された記法
static_assert(case_b{1, 2, 3.0f}.c == 3.0f);   // 普通の集成体と同じなので便利
static_assert(case_a{{1, 2}, 3.0f}.c == 3.0f); // C++17以前の代替方法

static_assert(std::is_constructible_v<case_a, int, int, float>);    // エラー
static_assert(std::is_constructible_v<case_b, int, int, float>);    // OK
static_assert(std::is_constructible_v<case_a, case_a_base, float>); // OK

さて、上記コードをVSCODEにでもコピペすると、たしかにstd::is_constructible_v<case_a, int, float>falseになっていることがわかるでしょう。

おそらく、内部的にはcase_a{1, 2, 3.0f}case_a{{1, 2}, 3.0f}case_a(case_a_base, float)が呼ばれているのだと思われます。

C++20で許可された丸括弧での初期化を「C++17で拡張された集成体初期化」の記法で書けない

また、C++20で許可された集成体の丸括弧での初期化も、「C++17で拡張された集成体初期化」の記法で記述することができません。

int main() {
  case_a a(1, 2, 3.0f);   // エラー
  case_b b(1, 2, 3.0f);   // OK
  case_a c({1, 2}, 3.0f); // OK
}

まとめ

C++17にて「継承を含む集成体」をまるで「一つの集成体」であるように初期化できるようになりましたが、上記のように現状では違うものとして扱わないと想定外のエラーに困らされるかもしれません。(実際に私自身が困ることになったので、今回記事にしたわけですが)

構築可不可については、C++20以上ならrequires節を使って独自に判定するコンセプトを作れますが、丸括弧初期化については標準(かコンパイラが独自に)対応しないとどうにもならないでしょうね。

お目汚し失礼いたしました。

Discussion