【C++】非型テンプレート・パラメータ【用途別の使い方】


非型テンプレート・パラメータ

非型テンプレート・パラメータ(Non-type template parameter)によって、テンプレート引数に型以外の定数値を指定すること可能になります。

テンプレートのパラメータでは通常typename Tという形で型名を指定しますが、本機能ではint Vという形で定数が渡せるようになります。これらは「非型テンプレート引数(Template non-type arguments)」と呼ばれることもあります。

パラメータとして渡せる値は「定数、定数式、関数、構造体、ポインタ」等様々です。

目次

# 定数
# 定数式
# 実数型(利用不可)
# 文字列ポインタ(利用不可)
# ポインタ
# 構造体
# 静的メンバ
# 関数ポインタ
# 非型テンプレート・パラメータの利用用途

定数の利用

テンプレートのパラメータとして数値を渡せるようになります。

template<int SERIES> void Cyberdyne() {
   printf("T-%d\n", SERIES);
}

Cyberdyne<800>(); // "T-800"
Cyberdyne<850>(); // "T-850"

式の評価が可能

式の計算結果を渡すことも可能です。

template<int V> int int_cast() { return V; }

int c = int_cast< 1 + 2 >(); // OK
int c = int_cast<(1 > 2)>(); // OK
// ERROR: Cannot initialize a variable of type 'int' with an lvalue of type '<overloaded function type>'
int b = int_cast< 1 > 2 >();

比較演算子>についてはテンプレートの閉じ括弧との関係でコンパイラ側の構文解釈が曖昧になってしまうため、式を丸カッコ()で囲う必要があります。

実数型は利用できない

実数型(float/double)は指定出来ません。

// A non-type template parameter cannot have type 'double'
template<int SERIES, double VERSION> void Cyberdyne() {
   printf("T-%d Version:%f\n", SERIES, VERSION);
}

Cyberdyne<800, 2.4>(); // NG

文字列ポインタも利用できない

基本的に文字列ポインタ(char*等)も指定出来ません。

特殊な方法でポインタを利用する方法は後々説明します。(# ポインタの利用)。
template<int SERIES, const char* COMMENT> void Cyberdyne() {
   printf("T-%d Comment:%s\n", SERIES, COMMENT);
}

Cyberdyne<800, "I'll be back">(); // ERROR: No matching function for call to 'Cyberdyne'
Cyberdyne<850, "You're terminated">(); // ERROR: No matching function for call to 'Cyberdyne'

ポインタの利用

グローバル変数等、リンケージを持った変数であれば、ポインターの利用も可能になります。またポインタは参照という形で受け取る必要があります。

template<const char*& STR> size_t GetStringLength() { return strlen(STR); }
const char* str_a = "foo"; // ここがポイント

int main() {
   GetStringLength<str_a>(); // OK: 3
   GetStringLength<"bar">(); // ERROR: No matching function for call to 'GetStringLength'
}

ローカル変数のポインタも受け取れません。受け取れる参照は外部リンケージ/内部リンケージとして宣言されたものや無名名前空間で宣言されたものに限られます。

構造体の利用

ポインタと同様にリンケージを作成し、それらを参照する形で利用すれば構造体の指定も可能になります。参照の代わりにポインタを用いることも可能です。

struct Number { int val; };
template<Number& V> struct NumberAccessor {
   int  get()        { return V.val; }
   void set(int val) { V.val = val;  }
};

Number num_a{2}; // ここがポイント

int main() {
   NumberAccessor<num_a>().get(); // 2
   NumberAccessor<num_a>().set(4);
   NumberAccessor<num_a>().get(); // 4
   
   Number num_b{1}; // これは駄目
   NumberAccessor<num_b>().get();
   // ERROR: Non-type template argument refers to object 'num_b' that does not have linkage
}

静的メンバの利用

静的メンバを渡すことも可能です。

struct NumberTrait {
   static constexpr Number MAX{INT_MAX};
   static constexpr Number MIN{INT_MIN};
};
template <const Number& NUM> int GetNumberValue() { return NUM.val; }

GetNumberValue<NumberTrait::MAX>(); //  2147483647
GetNumberValue<NumberTrait::MIN>(); // -2147483648

関数ポインタの利用

typedef int(*fn_t)(int);
template<fn_t F> bool Validate(int ch) { return F(ch); }

Validate<isalpha>('a');  // true
Validate<islower>('A');  // false
Validate<isnumber>('1'); // true
ちなみにGCC拡張が有効な場合は「template<typeof(int(*)(int)) F>」という書き方が出来ます。

活用例

クラスで活用する場合は、デリータ/デアロケータ/ファイナライザを渡すと便利です(free関数やAPI固有のRelease関数等々)。

template<typeof(void(*)(void*)) Free> struct Cleanup {
   void *ptr;
   ~Cleanup() { Free(ptr); }
};

int main() {
   if (true) {
      Cleanup<free> heap{malloc(sizeof(int))};
   }
   // "heap.ptr" was already dead
}

非型テンプレート・パラメータの利用用途

先程のデリータの例の他に、固定長配列のサイズやデフォルト値等の固定値を指定する際に活用すると良いです。

template<size_t Size, int Terminator>
struct Array { /* SafeImmutableArray */
   int _ary[Size];
   int operator[](size_t i) {
      return i < Size ? _ary[i] : Terminator;
   }
};

Array<3, -1> ary = {1, 2, 3};
printf("%d", ary[0]); //  1
printf("%d", ary[1]); //  2
printf("%d", ary[9]); // -1
printf("%d", Array<0, 0>()[0]); // 0

広告