コンパイル時の文字列結合 in C++

# はじめに

数値計算を行う際、入力に依存しない計算や操作は全てコンパイル時に済ませてしまいたい。特にログ出力における文字列の結合とか。

ということで「コンパイル時文字列結合」について検索してみると[Sprout](https://github.com/bolero-MURAKAMI/Sprout) という`constexpr` に関するライブラリが存在することを知る。その中に[文字列を扱うクラス](http://boleros.hateblo.jp/entry/20110926/1318115291)`sprout::string` なるものもあるではないか。この`sprout::string` クラスを利用すればコンパイル時文字列結合ができるので目的は達成される。これを使えば良い。

せっかくなのでもう少し検索してみるとStack Overflowで`std::array` を使ったコンパイル時文字列結合のコードが簡潔に書かれているのを見つけた（そのページへの[リンク](https://stackoverflow.com/questions/13292237/concat-two-const-char-string-literals/13294458#13294458)）。それを読んだところ勉強になったので、まとめノートを作成することにした。

# 使用したもの

- macOS Big Sur (ver. 11.2.2)
- clang++ (version 12.0.0)
- コンパイルオプション -std=c++11

# 実行してみる

## 元々のソースコード

Stack Overflowに記載されていたソースコードを、この記事のために少々手を加えて書いておく。変更点は以下。

- `char const` を`const char` にした。後者の方が書き慣れているから。好みの問題。
- 記事で参照しやすいように関数や構造体に異なる呼び名をコメントにて付与した。こうするとオーバーロードした関数のどれが呼ばれるのかがわかりやすいと思った。
- `unsigned` を`std::size_t` に変更した。元のコードを改良してみようと色々いじっている時に「`unsigined int` の部分が`unsigned long` になっている」という旨のコンパイルエラーと遭遇したので変更した（逆だったかも）。コードを改良しようとしなければ`unsigned` のままでも問題なく動くので、今回に関して言えば好みの問題。

```cpp:original.hpp
#include <array>

// 記事中での名称: テンプレート構造体seq
template<std::size_t... Is> struct seq{};

// 記事中での名称: テンプレート構造体gen_seq#N
template<std::size_t N, std::size_t... Is>
struct gen_seq : gen_seq<N-1, N-1, Is...>{};

// 記事中での名称: テンプレート構造体gen_seq#0
template<std::size_t... Is>
struct gen_seq<0, Is...> : seq<Is...>{};

// 記事中での名称: 可変引数テンプレート関数concat
template<std::size_t N1, std::size_t... I1, std::size_t N2, std::size_t... I2>
constexpr std::array<const char, N1+N2-1> concat(const char (&a1)[N1], const char (&a2)[N2], seq<I1...>, seq<I2...>){
  return {{ a1[I1]..., a2[I2]... }};
}

// 記事中での名称: テンプレート関数concat
template<std::size_t N1, std::size_t N2>
constexpr std::array<const char, N1+N2-1> concat(const char (&a1)[N1], const char (&a2)[N2]){
  return concat(a1, a2, gen_seq<N1-1>{}, gen_seq<N2>{});
}
```

## 実際に動かしてみる

実際にテンプレート関数`concat` を使ってコンパイル時文字配列を結合してみよう。以下のコードでは文字列`"Hello, "` と`world."` を持つ文字配列を結合している。テンプレート関数`concat` の戻り値は`std::array<const char, std::size_t N>` となっているので関数`std::puts` で出力するときは`data` メソッドで先頭ポインタを取得する。

```cpp:main.cpp
#include <cstdio>
#include "original.hpp"

int main ( const int argc, const char* const argv[] ){
  static constexpr char s1[] = "Hello, ";
  static constexpr char s2[] = "world.";
  static constexpr const auto s_array = concat( s1, s2 );
  std::puts( s_array.data() );
  return 0;
}
```

実行すると以下の出力が得られる。

```
Hello, world. 
```

なおテンプレート関数`concat` にはコンパイル時文字配列ではなく文字列リテラルをそのまま渡しても動く。

# 解説

## 実行順序

関数`concat(s1, s2)` が呼ばれたとき以下の順に呼ばれる。

1. テンプレート関数concat
2. 可変引数テンプレート関数concat

## テンプレート関数`concat`

```cpp
// 記事中での名称: テンプレート関数concat
template<std::size_t N1, std::size_t N2>
constexpr std::array<const char, N1+N2-1> concat(const char (&a1)[N1], const char (&a2)[N2]){
  return concat(a1, a2, gen_seq<N1-1>{}, gen_seq<N2>{});
}
```

この関数は「要素数_N1_ の`char` 型配列への参照」と「要素数_N2_ の`char` 型配列への参照」を引数に取り、_N1_ と_N2_ は非型テンプレートになっている。戻り値の要素数`-1` がついているのはヌル文字をひとつ減らすからだ。

この関数内では可変引数テンプレート関数`concat` を呼ぶとき引数に`gen_seq<N1-1>{}` と`gen_seq<N2>{}` を渡しているが、これはともにテンプレート構造体`gen_seq`#_N_ のオブジェクトである。

## テンプレート構造体`seq_gen`#_N_

```cpp
// 記事中での名称: テンプレート構造体seq
template<std::size_t... Is> struct seq{};

// 記事中での名称: テンプレート構造体gen_seq#N
template<std::size_t N, std::size_t... Is>
struct gen_seq : gen_seq<N-1, N-1, Is...>{};

// 記事中での名称: テンプレート構造体gen_seq#0
template<std::size_t... Is>
struct gen_seq<0, Is...> : seq<Is...>{};
```

テンプレート構造体`seq_gen`#_N_ では[可変引数テンプレート](https://cpprefjp.github.io/lang/cpp11/variadic_templates.html)が使われている。`std::size_t... Is` の部分で先頭以外のテンプレート引数をまとめて受け取り（空の場合もある）、基底構造体の指定で書かれている`Is...` の部分で展開される。このテンプレート構造体はテンプレート引数は異なるが自身を継承している。つまり、このテンプレート構造体の継承関係は再帰的になっているのだ。再帰構造を抽象的なまま理解するのは難しいのでまずは具体的に_N_ = 3 として展開してみよう。

- `seq_gen<3>` は非型テンプレートは`N` = 3 で可変引数テンプレートは空なので`seq_gen<2,2>` から派生している。
- `seq_gen<2,2>` は非型テンプレートが`N` = 2 で可変引数テンプレートは`Is...` = 2 なので`seq_gen<1,1,2>` から派生している。
- `seq_gen<1,1,2>` は非型テンプレートは`N` = 1 で可変引数テンプレートは`Is...` = 1, 2 なので`seq_gen<0,0,1,2>` から派生している。
- `seq_gen<0,0,1,2>` は非型テンプレートは_N_  = 0で可変引数テンプレートは`Is...` = 0, 1, 2 である。この`seq_gen<0,0,1,2>` はテンプレート構造体`seq_gen`#_N_ の部分特殊化された構造体であるテンプレート構造体`seq_gen`#0 である。そのため`seq_gen<0,0,1,2>` は構造体`seq<0,1,2>` から派生している。これはテンプレート構造体`seq` の実体のうちのひとつである。

以上より構造体`seq_gen<3>` は構造体`seq<0,1,2>` の派生構造体であることがわかる。帰納的に考えると`seq_gen<N>` は`seq<0,1,・・・,N-1>` の派生構造体である。（注意：ここでの「`・・・`」は言語仕様ではなく数字が順に並んでいることを表す表記法として使用しています。以下同様。）

テンプレート関数`concat` 内で可変引数テンプレート関数`concat` を呼ぶ際に`seq_gen<N1-1>` と`seq_gen<N2>` が渡されるが、可変引数テンプレート関数`concat` の定義をみるとその部分はテンプレート構造体`seq` として受け取っている。つまりテンプレート関数`concat` 内で可変引数テンプレート関数`concat` を呼ぶ時には引数として`seq<0,1,・・・,N1-2>` と`seq<0,1,・・・,N2-1>` を渡していることになる。

## 可変引数テンプレート関数`concat`

```cpp
// 記事中での名称: 可変引数テンプレート関数concat
template<std::size_t N1, std::size_t... I1, std::size_t N2, std::size_t... I2>
constexpr std::array<const char, N1+N2-1> concat(const char (&a1)[N1], const char (&a2)[N2], seq<I1...>, seq<I2...>){
  return {{ a1[I1]..., a2[I2]... }};
}
```

この関数は2つの「`char` 型配列への参照」と２つのテンプレート構造体`seq` を引数に取り、配列の要素数_N1_, _N2_ と`seq` の可変引数テンプレート引数`I1...`, `I2...` をテンプレート引数に取る。テンプレート関数`concat` から呼ばれる場合は`I1...`, `I2...` はそれぞれ`I1... = 0,1,・・・,N1-2` と`I2... = 0,1,・・・,N2-1`とに展開されるので、戻り値として渡される初期化子は`{{a1[0],a1[1],・・・,a1[N1-2],a2[0],a2[1],・・・,a2[N2-1]}}` と展開される。この初期化子内で`a1[I1]...` と書いている部分は[パラメータパックの拡張](https://cpprefjp.github.io/lang/cpp11/variadic_templates.html#ab5b8214ccd7d0c7e098c725031b4939311b934d)という機能である。

こうして第1引数の`char` 型配列のヌル文字を除いた要素と第2引数のヌル文字を含んだ要素を順に持つ`std::array` が戻り値として返されるのである。

# まとめ

今回、以下の項目について学ぶことができた。

- 配列への参照
- 可変引数テンプレート
- 可変引数テンプレートを用いた再帰的な定義

本当はここから改良して`char` 型配列もしくは`char*` で受け取れる戻り値を返す関数を作りたくて`std::initializer_list` を試すなどしてみたけどうまくいかなかった。しかし色々勉強できたのでとりあえずは満足した。

間違いや誤字脱字、理解不足な点があれば指摘していただけると助かります。