C++17までのテンプレート引数を通した文字列のやり取りを行う際、定数の参照や、クラス内で静的定数を定義した型を介して行う必要がありました1。
この方法では一つ値を渡すためだけにいちいち宣言なり定義なりを行わなければならず、手間がかかってとても面倒くさいと思っていました(特に識別子の名前を考えるところ)。
しかし、C++20より、非型テンプレートパラメータの条件が拡大され、定数式上で評価可能な多くのクラス型を指定することが可能になりました。
上記に併せてテンプレート引数推論を用いることで、非型テンプレートパラメータへカジュアルに文字列リテラルを渡せます。
この記事では文字列リテラルを非型テンプレートパラメータに指定できるような、簡単なクラスを作成していこうと思います。
なお、本記事の掲載ソースはVisual Studio 2022についてくるMSVCによりコンパイル、動作確認を行いました。
やりたいこと
以下のようなソースでコンパイルでき、テンプレートパラメータに指定した文字列を参照できるようにすることが目標です。
実際に利用可能なコーディング例は後述の「実装例」で解説します。
template <StringType Str>
struct A
{
static constexpr auto value = Str;
};
int main()
{
// valueから文字列定数"string_literal"へアクセス可能であること
A<"string_literal">::value;
}
実装例
1.固定長文字列クラスの作成
constexpr
変数を初期化できるような、文字列クラスを作成します。
文字列操作や比較等の機能については、本記事の趣旨からは逸脱すると考えますので省略します。
固定長のchar
型配列と、文字列リテラルから配列の長さを推論するためのコンストラクタを持つように定義します。
※定義の仕方によっては推論補助を書く必要があると思われますのでご注意を。
※std::array
あたりを継承すると、要素アクセス周りとかそろっているのでいいかも。辞書順で比較できるよう演算子定義するだけである程度体裁が整いそう。
// "Ce"はconstexprのつもり
template <int Size>
struct CeString
{
static constexpr int length = Size - 1;
// 文字列リテラルより推論を行うためのコンストラクタ
constexpr CeString(const char (&s_literal)[Size])
{
for (int i = 0; i < Size; i++) buf[i] = s_literal[i];
// 文字列リテラル以外が渡されても文字列としての体裁を保つため、一応最終要素には終端文字を入れておく
buf[length] = '\0';
}
// 文字列格納領域
char buf[Size];
};
なお、受け取り側のテンプレートパラメータをauto
とすることで、直接リテラルを...とはいきませんが、初期化と同時に文字列を渡すことができます。
これだけでもC++17までとは大違いです。
template <auto V>
struct A
{ static constexpr auto value = V; };
int main()
{
// CeStringを初期化しがてら、非型テンプレートパラメータとして指定
const char* str = A<CeString{"abcdef"}>::value.buf;
std::cout << str << std::endl; // abcdefと出力
return 0;
}
2.文字列リテラルを受け取るテンプレートの定義
非型テンプレートパラメータとして、文字列リテラルを受け取ることが可能なテンプレートクラス/テンプレート関数を定義します。
C++20ではテンプレートクラスのテンプレートパラメータを未決定のまま、非型テンプレートパラメータの型として書くことができるため、これを利用します。
「1.固定長文字列クラスの作成」で定義した固定長文字列クラスCeString
をテンプレートパラメータSize
を未指定のまま、非型テンプレートパラメータの型とします。
※非型テンプレートパラメータとしてクラス型を許可するより、以下の記述が、パラメータ未決定のテンプレートクラスでも非型テンプレートパラメータの型として指定できることの説明に該当すると思われます。
推論用クラス型のプレースホルダ (template A;があったときのA x{};のようなクラステンプレートのテンプレート引数推論を意図した型指定)
あらかじめ、固定長文字列クラスの要素数を文字列リテラルから推論できるようにすることで、パラメータ指定時にCeString
の要素数は自動的に決定されます。
// CeStringに具体的な要素数を明示しない
// テンプレートクラスの場合
template <CeString Str>
struct B
{ static constexpr auto str = Str; };
// テンプレート関数の場合
template <CeString Str>
auto func()
{
return std::string(Str.buf);
}
int main()
{
std::cout << B<"opqrst">::str.buf << std::endl; // opqrstと出力
std::cout << func<"uvwxyz12345">() << std::endl; // uvwxyz12345と出力
return 0;
}
以上の定義が、非型テンプレートパラメータへ文字列リテラルを渡すためのコーディング例となります。
3.実践
まとめ
お読みいただきありがとうございます。
C++17までよりずっと、テンプレートパラメータで直感的に値を扱うことが可能になったのですね。
同じような文字列型の静的定数を定義するクラスはテンプレートから流し込むことで定義の共通化をやりやすくなりそうですし、static_assert
やrequires
節で非型テンプレートパラメータに渡された文字列リテラルの検証を行い、コンパイル時にエラーとして検出できるようにするのも面白そうです。
なお、fixed_string
をキーワードに検索すると関連の情報やライブラリがヒットするようですので、そちらを参考にしたり、使用されるとよいと思います
参考文献
以下記事により、非型テンプレートパラメータへ文字列リテラルを渡せることを知りました。
感謝です。
-
マクロちゃんは知らない子です。 ↩