最終更新日時: 2025年02月26日 17時06分54秒 (JST)
Akira Takahashi が更新

履歴 編集

# 非型テンプレートパラメータとしてクラス型を許可する [P0732R2](C++20)

このページはC++20に採用された言語機能の変更を解説しています。

のちのC++規格でさらに変更される場合があるため関連項目を参照してください。

## 概要

これまで、非型テンプレートパラメータ (non-type template parameter) としては、整数型、ポインタ、参照、`std::nullptr_t`、プレースホルダ型の値しか受け取ることが許可されていなかった。

    template <int N>
    class X;

    X<3> x;

C++11以降での汎用的な定数式`constexpr`の導入により、多くのクラスオブジェクトを定数として扱えるようになったことを受けて、C++20ではそのように定数として扱える型を広く非型テンプレートパラメータとして受け取ることが許可される。

## 仕様

非型テンプレートパラメータとして、以下のいずれかに分類される型のオブジェクトを受け取ることができる：

- 構造的型
- プレースホルダ型を含む型 (`template <auto V> class X;`の`auto`)
- 推論用クラス型のプレースホルダ (`template <class T> A;`があったときの`A x{};`のようなクラステンプレートのテンプレート引数推論を意図した型指定)

構造的型 (structural type) とは以下のいずれかに分類される型である：

- スカラ型
- 左辺値参照型
- 以下の特徴をもつリテラルクラス型：
  - すべての基本クラスと非静的メンバ変数が`public`かつ非`mutable`であり、
  - すべての基本クラスと非静的メンバ変数が構造的型もしくはその (多次元) 配列である

## 備考

- C++20では`std::vector`および`std::basic_string`を定数式内で使用できるようになるが、これらの型はC++20時点では構造的型に分類されないため、そのオブジェクトをテンプレート引数として渡すことはできない
  - C++20 可変サイズをもつコンテナのconstexpr化

## 例

### floatやクラス型を非型テンプレートパラメータにする例

    #include <utility>

    struct X1 {
      int i;
      float f;

      friend bool operator==(const X1&, const X1&) = default;
    };

    struct X2 {
      int i;
      float f;

      constexpr X2(int i_, float f_)
        : i(i_), f(f_) {}

      friend bool operator==(const X2&, const X2&) = default;
    };

    template <int N>
    struct C1 {
      static constexpr int value = N;
    };

    template <float N>
    struct C2 {
      static constexpr float value = N;
    };

    template <X1 x1, X2 x2>
    struct C3 {
      static constexpr X1 value1 = x1;
      static constexpr X2 value2 = x2;
    };

    template <auto V>
    struct C4 {
      static constexpr decltype(V) value = V;
    };

    int main() {
      static_assert(C1<1>::value == 1);
      static_assert(C2<3.14f>::value == 3.14f);

      constexpr X1 x1{1, 3.14f};
      constexpr X2 x2{2, 5.27f};
      using c3 = C3<x1, x2>;
      static_assert(c3::value1 == x1);
      static_assert(c3::value2 == x2);

      static_assert(C4<1>::value == 1);
      static_assert(C4<3.14f>::value == 3.14f);
      static_assert(C4<x1>::value == x1);
      static_assert(C4<x2>::value == x2);

      constexpr std::pair p{1, 3.14};
      static_assert(C4<p>::value == p);
    }

#### 出力

### プレースホルダーを含む型を非型テンプレートパラメータにする例

    #include <iostream>

    template <class T>
    struct X {
      constexpr X(T x) : value(x) {}
      T value;
    };

    template <X x>
    struct Y {
      void print() {
        std::cout << x.value << std::endl;
      }
    };

    int main() {
      // Xクラステンプレートのテンプレートパラメータが
      // コンストラクタ引数`3`から推論される
      Y<3>{}.print();
    }

#### 出力

    3

### 文字列クラスオブジェクトを非型テンプレートパラメータにしてリテラル演算子を定義する例

    #include <iostream>
    #include <algorithm>

    template <typename CharT, std::size_t N>
    struct basic_fixed_string {
      constexpr basic_fixed_string(const CharT (&str)[N+1])
      { std::copy_n(str, N+1, data); }

      friend auto operator<=>(const basic_fixed_string&, const basic_fixed_string&) = default;
      CharT data[N+1];
    };

    template <typename CharT, std::size_t N>
    basic_fixed_string(const CharT (&str)[N]) -> basic_fixed_string<CharT, N-1>;

    template <std::size_t N>
    using fixed_string = basic_fixed_string<char, N>;

    namespace my_literals {
    template <basic_fixed_string str>
    auto operator""_udl() {
        return str;
    }
    }

    int main() {
      using namespace my_literals;
      auto s = "hello"_udl;
      std::cout << s.data << std::endl;
    }

#### 出力

    hello

## 備考

- Bug 97930 - `pair` is not a structural type
  - GCC 10では`pair`が構造的型とみなされないバグがある。GCC 11で修正済み

## 関連項目

- C++17 非型テンプレートパラメータの`auto`宣言

## 参照

- P0732R2 Class Types in Non-Type Template Parameters
- P1907R1 Inconsistencies with non-type template parameters