C++

テンプレート引数の整数

More than 1 year has passed since last update.

前置き

テンプレート引数には型のほかに整数を指定することもできます。
そのような場合の挙動を調べました。
サンプルとして以下のコードを使います。

クラステンプレートのサンプル
template <class T, int N=10>
class Sample {
    T data[N];
public:
    int size() const { return N; }
};
テンプレート関数のサンプル
template <class T, int N>
inline int size(const T (&a)[N]) { return N; }

クラステンプレートの動作

前提として、テンプレートはコンパイル時にクラスや関数の実体が生成されます。したがって、コンパイル時に決まる値のみテンプレート引数として使うことができます。

クラステンプレートを使う
#include <iostream>

 // OK
Sample<int, 3> s1;
std::cout << s1.size() << std::endl;

// OK, 省略されたNにはデフォルト引数の10が入る
Sample<int> s2;
std::cout << s2.size() << std::endl;

// NG, nは変化しうる値なのでテンプレート引数にできない
int n = 4;
Sample<int, n> s3;
std::cout << s3.size() << std::endl;

// こちらのnはconstなのでOK
const int n = 5;
Sample<int, n> s4;
std::cout << s4.size() << std::endl;

// NG, 'bar'はconstだが動的な値fooを経由している
int foo = 7;
const int bar = foo;
Sample<int, bar> s7;
std::cout << s3.size() << std::endl;    

// 配列サイズが0やマイナスになるのはもちろんNG
Sample<int, 0> s6;
Sample<int, -1> s7;
std::cout << s6.size() << "," << s7.size() << std::endl;

関数テンプレートの挙動

テンプレート引数が暗黙的に解決できる場合、通常の関数のように使えます。
特殊な型変換などをしたい場合は、テンプレート引数を明示してあげる必要があります。

関数テンプレートを使う
// OK
int a1[3] = { 3,4,5 };
std::cout << size(a1) << std::endl;    // 3

// OK
int a2[] = { 7,6,5 };
std::cout << size(a2) << std::endl;    // 3

// OK, a[2], a[3]は自動的に(0で?)初期化される
// 0初期化はテンプレートとは関係ない
int a3[4] = { 8,9 };
std::cout << size(a3) << std::endl;    // 4

// 配列サイズ0でNG
int a4[] = {};
std::cout << size(a3) << std::endl;

// NG, 型が合わない
int *a5 = new int[10];
std::cout << size(a5) << std::endl;

// NG, size_check関数内ではNがわからない
template <class T>
void size_check(T ar[])
{
    std::cout << size(ar) << std::endl;
    return;
}
int a6[3] = { 3,3,4 };
size_check(a6);

// OK, なぜかSTLは動的に変更してもテンプレートに渡せる…?
std::vector<int> a7 = { 1,1,2,3,5,8 };
int hoge = 13;
a7.push_back(hoge);
std::cout << size(a7) << std::endl;    // 7

動的に変更しているはずのvectorで正しい結果が返る理由はよくわかりません…。
コメントにて @yumetodo さんから補足がありました。ADL(Argument Dependent Look-up)という仕様による動作だそうです。

size - cpprefjp C++日本語リファレンス

なお、a5のような形式でも、テンプレートを追加すれば(だいぶ強引に)size関数を使えます。サイズを知るための関数に手動でサイズを指定するというのも不思議な話ですが。

動的配列へのテンプレート関数
template <class T, int N>
inline int size(const T* a) { return N; }

int* a5 = new int[10];
std::cout << size<int, 10>(a5) << std::endl;

参考資料

C++テンプレートテクニック 第2版
これを読み始めたばかりなので、認識や用語にかなりあやふやなところがあります。
お詳しい方がおられましたら、コメントにて補足やご指摘をお願い致します。