C++でテンプレートを使っていると、ときどき、文字列をテンプレートに渡したくなるときがあります。
ですが残念なことに、文字列をテンプレート引数にすることはできません。
// こういうことをやってみたい! (けど、できない) template<const char* S> struct Str { // ... }; typedef Str<"abcd"> ABCDE;
直接に文字列をテンプレート引数として使うことはできないのですが、先日、おもしろいことに気がつきました。
C++11では、constexpr
キーワードを使うことで、定数を与えた関数の値も定数として扱うことができるようになりました。
コンパイル時に値が決定する定数ですから、テンプレート引数としても使うことができます。
// N と M を加算するテンプレート template<int N, int M> struct Add { static const int value = N + M; }; // コンパイル時に定数が与えられれば定数として扱える関数 constexpr int abs(int n) { return (n >= 0) ? n : -n; } // 関数の値をテンプレート引数として与えることができる int n = Add<abs(-10), abs(5)>::value;
実行結果。n
に 15 という値が設定されています。
.section __TEXT,__text,regular,pure_instructions .section __DATA,__data .globl _n ## @n .align 2 _n: .long 15 ## 0xf
さらに加えて。以前、関数はテンプレート引数になるというエントリを書きました。
文字列を文字列という形でテンプレートに渡すことはかないませんが、たとえば次のようにすれば、文字のシーケンスをテンプレートに渡すことができます。
static constexpr char abcd(int n) { return "abcd"[n]; }
また文字のシーケンスに含まれる値(=文字)も定数として取り出せますから、それをされにテンプレートの引数として渡すことができることになります。
これらをふまえて。
// 終端 struct Nil {}; // 文字-型変換テンプレート template<char C> struct Char { static const char value = C; }; // 後続の型を定義するテンプレート(先行宣言) template<constexpr char (&String)(int), int N, typename C> struct Tail; // 文字列からシーケンスを生成するテンプレート template<constexpr char (&String)(int), int N = 0> struct StringSequence { typedef Char<String(N)> head; typedef typename Tail<String, N, head>::type tail; }; // 後続の型を定義するテンプレート template<constexpr char (&String)(int), int N, typename C> struct Tail { typedef StringSequence<String, N + 1> type; }; // 後続の型を定義するテンプレート(終端) template<constexpr char (&String)(int), int N> struct Tail<String, N, Char<'\0'>> { typedef Nil type; }; // 以下、利用例 // 文字列定数を表現する関数 static constexpr char abcd(int n) { return "abcd"[n]; } // 間接的に文字列を受け取るテンプレートから型を生成 typedef StringSequence<abcd> ABCD; // 値を取り出す char a = ABCD::head::value; char b = ABCD::tail::head::value; char c = ABCD::tail::tail::head::value; char d = ABCD::tail::tail::tail::head::value; char eos = ABCD::tail::tail::tail::tail::head::value; // end of string
実行結果。変数 _a
, _b
, _c
, _d
にそれぞれの文字コードが格納されています。eos
の値は 0
で初期化された状態を維持しているため、値は明示的には格納されていません。
.section __TEXT,__text,regular,pure_instructions .section __DATA,__data .globl _a ## @a _a: .byte 97 ## 0x61 .globl _b ## @b _b: .byte 98 ## 0x62 .globl _c ## @c _c: .byte 99 ## 0x63 .globl _d ## @d _d: .byte 100 ## 0x64 .globl _eos ## @eos .zerofill __DATA,__common,_eos,1,0
このままではメリットは感じられませんが、文字列がシーケンスになったということで 昨日の畳み込みテンプレート の引数に渡すことができるようになったということです。
つづく。