1. ホーム
  2. c++

[解決済み] C++でコンパイル時の文字列を簡便に宣言する方法

2022-04-28 21:11:18

質問

C++でコンパイル時に文字列を作成し操作できることは、いくつかの有用な用途があります。C++でコンパイル時に文字列を作成することは可能ですが、その処理は非常に面倒で、文字列を例えば文字の変種列として宣言する必要があるからです。

using str = sequence<'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!'>;

文字列の連結や部分文字列の抽出などの操作は、文字の並びに対する操作として簡単に実装することができます。 コンパイル時の文字列をもっと便利に宣言することはできないのでしょうか?もしそうでなければ、コンパイル時の文字列を便利に宣言できるような提案はありますか?

既存のアプローチが失敗する理由

理想的には、以下のようにコンパイル時の文字列を宣言できるようにしたいものです。

// Approach 1
using str1 = sequence<"Hello, world!">;

または、ユーザー定義のリテラルを使用します。

// Approach 2
constexpr auto str2 = "Hello, world!"_s;

ここで decltype(str2) を持つことになります。 constexpr コンストラクタを使用します。ということを利用して、アプローチ1のもっと雑なバージョンも実装可能です。

template <unsigned Size, const char Array[Size]>
struct foo;

ただし、配列には外部リンクが必要なので、アプローチ1を実現するためには、次のような書き方をしなければなりません。

/* Implementation of array to sequence goes here. */

constexpr const char str[] = "Hello, world!";

int main()
{
    using s = string<13, str>;
    return 0;
}

言うまでもなく、これは非常に不便なことです。アプローチ2は、実は実装が不可能なのです。仮に、( constexpr ) リテラル演算子の戻り値の型はどのように指定するのでしょうか?演算子は文字の可変長配列を返す必要があるので、そのためには const char* パラメータで戻り値の型を指定します。

constexpr auto
operator"" _s(const char* s, size_t n) -> /* Some metafunction using `s` */

この結果、コンパイルエラーになります。 sconstexpr . 以下のようにして回避しようとしても、あまり役に立ちません。

template <char... Ts>
constexpr sequence<Ts...> operator"" _s() { return {}; }

規格では、この特定のリテラル演算子の形式は、整数型と浮動小数点型に予約されていることになっています。一方 123_s を使えばうまくいくでしょう。 abc_s はできません。ユーザー定義リテラルを完全に捨て、通常の constexpr という関数があります。

template <unsigned Size>
constexpr auto
string(const char (&array)[Size]) -> /* Some metafunction using `array` */

先ほどと同じように、配列のパラメータを constexpr 関数は、それ自体がもはや constexpr の型になります。

文字列と文字列のサイズを引数にとり、文字列中の文字からなるシーケンスを返すCプリプロセッサマクロを定義することができるはずです(using..............................)。 BOOST_PP_FOR 文字列化、配列の添え字など)。しかし、私にはそのようなマクロを実装する時間(あるいは十分な興味)がありません =)。

どのように解決するのですか?

のエレガンスに匹敵するものを見たことがない。 Scott Schurrの str_const で発表しました。 C++ Now 2012 . これには constexpr とはいえ

その使い方と、できることを紹介します。

int
main()
{
    constexpr str_const my_string = "Hello, world!";
    static_assert(my_string.size() == 13, "");
    static_assert(my_string[4] == 'o', "");
    constexpr str_const my_other_string = my_string;
    static_assert(my_string == my_other_string, "");
    constexpr str_const world(my_string, 7, 5);
    static_assert(world == "world", "");
//  constexpr char x = world[5]; // Does not compile because index is out of range!
}

コンパイル時の範囲チェックほどクールなものはないでしょう!

使い方も、実装も、マクロを使わない。 また、文字列のサイズに人為的な制限もない。 実装をここに掲載したいところだが、Scottの暗黙の著作権を尊重する。 実装は、上でリンクした彼のプレゼンテーションの1枚のスライドにある。

C++17のアップデート

この回答を投稿してからの数年間で std::string_view は、私たちの道具箱の一部となりました。 以下は、上記を以下のように書き換えたものです。 string_view :

#include <string_view>

int
main()
{
    constexpr std::string_view my_string = "Hello, world!";
    static_assert(my_string.size() == 13);
    static_assert(my_string[4] == 'o');
    constexpr std::string_view my_other_string = my_string;
    static_assert(my_string == my_other_string);
    constexpr std::string_view world(my_string.substr(7, 5));
    static_assert(world == "world");
//  constexpr char x = world.at(5); // Does not compile because index is out of range!
}