エンジニアのソフトウェア的愛情

または私は如何にして心配するのを止めてプログラムを・愛する・ようになったか

文字列をリンクトリストにする、ただしC++テンプレートで

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

このままではメリットは感じられませんが、文字列がシーケンスになったということで 昨日の畳み込みテンプレート の引数に渡すことができるようになったということです。


つづく。