読者です 読者をやめる 読者になる 読者になる

ボレロ村上 - ENiyGmaA Code

中3女子です。

Sprout.String - constexpr 文字列クラス

Sprout.String - constexpr 文字列クラス


sprout/string.hpp Github https://github.com/bolero-MURAKAMI/Sprout/blob/master/sprout/string.hpp

Sprout.String は、constexpr で使える(固定長)文字列クラスを提供します。

    • モチベーション

constexpr の規格化によって、コンパイル時の文字列処理・解析といったことが実現可能になりましたが、
文字列をリテラルのまま使い回すのはかなり厳しいし、アルゴリズムにも適用しづらい。

そういった場合、constexpr で使える文字列クラスがあれば便利だろう、ということでつくりました。

#include <sprout/string.hpp>

ヘッダは sprout/string.hpp です。

static constexpr auto str1 = sprout::to_string("foobar1234");
static constexpr auto str2 = sprout::to_string("hogehoge");

static_assert(std::is_same<decltype(str1), sprout::basic_string<char, 10> const>::value, "");
static_assert(std::is_same<decltype(str1), sprout::basic_string<char, 8> const>::value, "");

リテラルから string への変換は to_string() で行います。


string 型の宣言は以下のようになっています。

template<typename T, std::size_t N, typename Traits = sprout::char_traits<T> >
class basic_string;

T は文字型、 N はバッファの長さです。
std::array などと違うのは、(N == 文字列の長さ)とは限らないということです。
N は、「この文字列がとることのできる最大の長さ」になります。

static_assert(*str1.begin() == 'f', "");
static_assert(*(str1.end() - 1) == '4', "");
static_assert(*str1.cbegin() == 'f', "");
static_assert(*(str1.cend() - 1) == '4', "");
static_assert(*str1.rbegin() == '4', "");
static_assert(*(str1.rend() - 1) == 'f', "");
static_assert(*str1.crbegin() == '4', "");
static_assert(*(str1.crend() - 1) == 'f', "");

begin(), end() などのイテレータは勿論 constexpr です。

    • サイズ
static_assert(str1.size() == 10, "");
static_assert(!str1.empty(), "");
static_assert(str1.max_size() == 10, "");

サイズの取得も constexpr です。
なお、(max_size() == N)は常に真です。

    • 要素の参照
static_assert(str1[0] == 'f', "");
static_assert(str1[9] == '4', "");
assert(str1.at(0) == 'f');
assert(str1.at(9) == '4');
static_assert(str1.front() == 'f', "");
static_assert(str1.back() == '4', "");

operator[] は constexpr です。
ちなみに、at() は constexpr ではありません。

※2012/05/17 追記
現在の実装では、at() も constexpr になっています。

    • NULL終端文字列
static_assert(str1.c_str()[0] == 'f', "");
static_assert(str1.data()[0] == 'f', "");

NULL終端文字列へのポインタを得るには c_str() を使います。
data() にはconst版と非const版があります。

    • 比較
static_assert(!(str1 == str2), "");
static_assert(str1 != str2, "");
static_assert(str1 < str2, "");
static_assert(!(str1 > str2), "");
static_assert(str1 <= str2, "");
static_assert(!(str1 >= str2), "");

static_assert(str1.compare("foobar1234") == 0, "");
static_assert(str1.compare("zzzz") < 0, "");
static_assert(str1.compare("aaaa") > 0, "");

各比較演算子が使えます。
strcmp() 風の compare() も使えます。

    • 切り出し
{
    static constexpr auto str3 = str1.substr();
    static_assert(str3 == "foobar1234", "");
    static_assert(std::is_same<decltype(str1), decltype(str3)>::value, "");
}
{
    static constexpr auto str3 = str1.substr(6);
    static_assert(str3 == "1234", "");
    static_assert(std::is_same<decltype(str1), decltype(str3)>::value, "");
}
{
    static constexpr auto str3 = str1.substr(0, 6);
    static_assert(str3 == "foobar", "");
    static_assert(std::is_same<decltype(str1), decltype(str3)>::value, "");
}

部分文字列の切り出しには substr() を使います。

    • 連結
{
    static constexpr auto str3 = str1 + str2;
    static_assert(str3 == "foobar1234hogehoge", "");
}
{
    // ! Error in GCC4.7
    //static constexpr auto str3 = str1 + to_string("hogehoge");
    //static_assert(str3 == "foobar1234hogehoge", "");
}

operator+() で文字列の連結ができます。

なお、オペランドに一時オブジェクトを渡すと何故かコンパイルエラーになります。
GCC4.7 のバグかもしれません。

※2011/10/09 追記
上記のコメントアウト部分をコンパイルするには、

#define SPROUT_CONFIG_SUPPORT_TEMPORARY_CONTAINER_ITERATION

を定義することで可能になります。

※2012/05/17 追記
現在の実装では、デフォルトで一時オブジェクトを渡すことができます。

{
    // ! OK
    static constexpr auto str3 = str1 + to_string("hogehoge");
    static_assert(str3 == "foobar1234hogehoge", "");
}
    • NULL終端文字列からの変換
{
    static constexpr auto str3 = sprout::string_from_c_str<10>(str1.c_str());
    static_assert(str3 == "foobar1234", "");
}
{
    static constexpr auto str3 = sprout::string_from_c_str<10>(str1.c_str(), 6);
    static_assert(str3 == "foobar", "");
}

NULL終端文字列からの変換には string_from_c_str() を使います。
なお、テンプレート引数にバッファの長さ(N)を指定してやる必要があります。


    • 代入
{
    auto s = sprout::to_string("abc");
    s.assign(to_string("ABC"));
    assert(s.size() == 3);
    assert(s[0] == 'A');
}
{
    auto s = sprout::to_string("abc");
    s.assign(to_string("ABC"), 0, 2);
    assert(s.size() == 2);
    assert(s[0] == 'A');
}
{
    auto s = sprout::to_string("abc");
    s.assign("ABC", 2);
    assert(s.size() == 2);
    assert(s[0] == 'A');
}
{
    auto s = sprout::to_string("abc");
    s.assign("ABC");
    assert(s.size() == 3);
    assert(s[0] == 'A');
}
{
    auto s = sprout::to_string("abc");
    s.assign(1, 'A');
    assert(s.size() == 1);
    assert(s[0] == 'A');
}

{
    auto s = sprout::to_string("abc");
    s = to_string("ABC");
    assert(s.size() == 3);
    assert(s[0] == 'A');
}
{
    auto s = sprout::to_string("abc");
    s = "ABC";
    assert(s.size() == 3);
    assert(s[0] == 'A');
}
{
    auto s = sprout::to_string("abc");
    s = 'A';
    assert(s.size() == 1);
    assert(s[0] == 'A');
}

assign() や operator=() で代入が出来ます。
これらは当然ながら constexpr ではありません。
なお、元のバッファ長(N)を越える長さを格納することは出来ません。

    • ストリーム入出力
{
    std::ostringstream os;
    os << str1;
    assert(os.str() == "foobar1234");
}

{
    std::istringstream is("hogehoge piyopiyo");
    auto str3 = str1;
    is >> str3;
    assert(str3 == "hogehoge");
}

ストリーム入出力が定義されています。
operator<<() は通常の文字列出力と同じ。
operator>>() は空白の区切りかEOFまで、または最大のバッファ長(N)まで読み込みます。


このほか、いくつかのメタ関数や、Sprout のアルゴリズムで使うためのトレイトの特殊化などが定義されています。

replace() や find() などいくつかのメンバ関数はまだ実装されていません。
これらはいずれまた実装する予定です。