SRELL

 SRELL (std::regex-like library) はC++用の正規表現テンプレートライブラリです。

目次

概要

std::regexと同じクラス構成

 SRELLはECMAScript (JavaScript) 互換の正規表現エンジンを、"std::regex"(C++11で導入された正規表現ライブラリ)とクラス構成が同じになるようにラッピングしたものです。クラスデザインが同じですので、std::regexや、その基となったboost::regexと同じ感覚で扱えます。

Unicodeに特化した実装

 SRELLはUnicodeに特化した正規表現ライブラリです。

  • 特別な設定をせずとも既定でUTF-8/UTF-16/UTF-32文字列が扱えます。'.' がUTF-16文字列でサロゲートペアの片割れだけにマッチしたり、UTF-8文字列のコードユニットにマッチしたりするようなことがありません。
  • 文字クラス内でプレーン1以降の文字も [丈𠀋] のように指定できます。また [\u{1b000}-\u{1b0ff}] のような範囲指定もできます。
  • ギリシア文字のΣのように小文字が2種類ある字(\u03c2 [ς] と \u03c3 [σ])や、クロアチア語で使われるラテン文字DŽのように、「大文字DŽ (upper-case)」・「小文字dž (lower-case) 」に加えて、「頭文字Dž (title-case)」なる第3のcaseがある文字でも、icase検索(大文字・小文字を区別しない検索)時にきちんと処理されます。

※ちなみにC++11のstd::regexは、us-asciiやiso-8859-*のような1文字が固定長の文字コードを前提としています。そのため1文字が可変長であるUTF-8やUTF-16の文字列はうまく扱えません。
 C++0x(現C++11)にchar16_t/char32_t型が導入されることになった時に、C++の作業部会内でu16stringやu32stringに加えてregexも対応するかどうか議論があったようですが、反対意見が多かったため見送られてそのままになっているようです。

 C++11に向けた改訂作業の中でもregexは比較的初期に提案された拡張であったことから、まったくと言って良いほどC++11の新機能に依存していません。そのためC++11より前のコンパイラであっても、C++のテンプレートを正しく解釈するものであればSRELLは利用可能です(動作確認済みコンパイラのうちもっとも古いものはVC++2005です)。

Download

使い方

 パスの通ったところに srell*.hpp(srell.hpp, srell_ucfdata.hpp, srell_updata.hpp の3ファイル)を置いて srell.hpp をincludeするだけです。

//  Example 01:
#include <cstdio>
#include <string>
#include <iostream>
#include "srell.hpp"

int main()
{
    srell::regex e;     //  正規表現オブジェクト。
    srell::cmatch m;    //  結果を納めるオブジェクト。

    e = "\\d+[^-\\d]+"; //  正規表現をコンパイル。
    if (srell::regex_search("1234-5678-90ab-cdef", m, e))
    {
        //  printfを使うなら。
        const std::string s(m[0].first, m[0].second);
            //  上は下のどちらかでも良い。
            //  const std::string s(m[0].str());
            //  const std::string s(m.str(0));
        std::printf("result: %s\n", s.c_str());

        //  iostreamを使うなら。
        std::cout << "result: " << m[0] << std::endl;
    }
    return 0;
}
	

 この例のように、SRELLを構成するクラスやアルゴリズムはすべてnamespace srellの下に置かれています。この点を除けば使い方はstd::regexに準じます。

 現時点ではまだstd::regexに関する日本語の文書があまりないようですので、さしあたってSRELLを使ううえで必要となりそうな情報を次のページにまとめました。

 Zipアーカイヴ内の reame_ja.txt も併せてご覧ください。

C++11の機能

 C++11で導入された機能のうち、std::regexが利用することもあるのは以下の3つです。

 2019年 5月現在、SRELLではこれらの使用可否を次のようにして判定しています。

#if defined(__cpp_unicode_characters) && !defined(SRELL_CPP11_CHAR1632_ENABLED)
  #define SRELL_CPP11_CHAR1632_ENABLED  //  char16_t, char32_t用のtypedefを行う。
#endif
#ifdef __cpp_initializer_lists
  #include <initializer_list>
  #ifndef SRELL_CPP11_INITIALIZER_LIST_ENABLED
    #define SRELL_CPP11_INITIALIZER_LIST_ENABLED  //  引数にinitializer_listを渡せるようにする。
  #endif
#endif
#if defined(__cplusplus) && __cplusplus >= 201103L
  #ifndef SRELL_CPP11_MOVE_ENABLED
    #define SRELL_CPP11_MOVE_ENABLED  //  コンストラクタや代入でmoveを有効にする。
  #endif
#endif
		

 実際には該当する機能が使えるにもかかわらず、コンパイラが適切に__cpp_*マクロや__cplusplusの値を設定しないためにC++11の機能が有効にならない場合、SRELLをincludeする前に上記マクロのうち必要なSRELL_CPP11_*を定義しておくと、対応する機能を強制的にオンにすることが出来ます

C++20の機能

 C++20ではUTF-8専用の型であるchar8_tが導入される予定です。GCC 9以降やClang 7以降では、コンパイル時に-fchar8_tオプションを付加することでchar8_tを有効にすることが出来ます。

 SRELLでもversion 2.100より試験的にchar8_tの対応を始めました。

 なおC++とUnicode、char8_tについては別頁にまとめてあります。

※コンパイラのchar8_t対応を有効にすると、u8'a'u8"abc"のようなu8 prefix付きの文字リテラルや文字列リテラルはchar型ではなくなります。std::string s(u8"あいう");のようなコードは型の不一致でコンパイルエラーになることにご注意ください。

参考情報

 SRELLの動作確認には主にVCとMinGWを使用していますが、Compiler Explorer上で簡単にテストしてみたところによりますと、2019年5月現在、少なくとも次の環境でもSRELL 2.200を使ったサンプルコード(srell::u16regexを使用してUTF-16文字列に対する正規表現検索をするもの)からバイナリの生成ができるようです。

 charやwchar_tのみを使う場合、Compiler Explorerで一番古いと思われるx86-64 gcc 4.1.2でもビルド可能なようです。

SRELLの正規表現

 SRELL 2ではECMAScript 2018 (ES9.0) のRegExp互換の正規表現が使えます。

 具体的には次の通りです

SRELLで使用可能な正規表現一覧
文字
.

改行以外の文字にマッチ。
(ECMAScriptにおける改行文字は、U+000A, U+000D, U+2028, U+2029の4文字。ECMAScript 2018規格の11.3を参照)

パターンコンパイル時にdotallオプションが指定されていると、前記4文字も含むすべての文字にマッチする([\u{0}-\u{10ffff}]と等価)。Perl 5の//sに相当。
(dotallオプションフラグはSRELL 2.000以降で使用可能。ES2018/ES9.0でRegExpに導入された機能)

\0

NULL文字 (\u0000) にマッチ。

\t

水平タブ (\u0009) にマッチ。

\n

Line Feed (\u000a) にマッチ。

\v

垂直タブ (\u000b) にマッチ。

\f

Form Feed (\u000c) にマッチ。

\r

Carriage Return (\u000d) にマッチ。

\cX

Xの文字コード & 0x1fに相当するコントロール文字にマッチ。Xの範囲は [A-Za-z] のみ有効。
"\c" の後ろにA-Zまたはa-zが続いていない時はerror_escapeがthrowされてくる。

\\

バックスラッシュそのもの (\u005c) にマッチ。

\xhh

2桁の16進数hhで表されるUnicodeのコードポイントにマッチ。
"\x" の後ろに2桁の16進数が続いていない時はerror_escapeがthrowされてくる。

\uhhhh

4桁の16進数hhhhで表されるUnicodeのコードポイントにマッチ。
"\u" の後ろに4桁の16進数が続いていない時はerror_escapeがthrowされてくる。

\u{h...}

1桁以上の16進数h...で表されるUnicodeのコードポイントにマッチ。
"\u{...}" の {} 内が1桁以上の16進数でない時や、コードポイントの上限値を超えている時、閉じ '}' がない時などにはerror_escapeがthrowされてくる。

ECMAScript 6にて追加された表現です。提案書の段階では{...}内は「1~6桁の16進数」とされていたのですが、ECMAScript仕様への追加が決まった際に「1桁以上の16進数」に変更されていたようです。この変更に長らく気づかなかったため、SRELL 2.001までは提案書に基づく実装となっています。

\

'\' の後ろにエスケープ文字として認識されない文字が続いている時は、その文字そのものにマッチ。例えば "\{" は '{' にマッチする。
文字列が '\' で終わるなど '\' の使い方が不正な時にはerror_escapeがthrowされてくる。

選択
abc|def

文字列abcまたは文字列defにマッチ。

文字クラス
[]

文字クラス。

  • [ABC]……AかBかCかにマッチ。
  • [^DEF]……最初が^の時は補集合。この場合、DでもEでもFでもない文字にマッチ。
  • [G^H]……冒頭以外にある^は、^そのものを表す。この場合Gか^かHかにマッチ。
  • [I-K]……文字1 - 文字2という並びは範囲指定。この場合はIかJかKかにマッチ。
  • [-LM]……上記のような並び以外に位置する '-' は '-' そのものを表す。この場合は-かLかMかにマッチ。
  • [N-P-R]……範囲指定直後の '-' も '-' そのものを表す。この場合はN, O, P, -, Rのいずれかにマッチ。Qは含まれず。
  • [.|({]……'.' か '|' か '(' か '{' かにマッチ。'.', '|', '(', '{' も、文字クラスの中ではその特殊性を失う。
  • []……空集合。どの文字にもマッチせぬため、これが現れると照合は常にそこで失敗する。
  • [^]……空集合の補集合。どの文字にもマッチする。

Perlの正規表現には「'[' の直後にある ']' は ']' そのものを表す」という特例があるが、ECMAScriptの正規表現にはそのような例外はない。従って ']' を使うには '\' でエスケープする必要がある。

'[' と ']' とが非対称な時にはerror_brackがthrowされる。また "[b-a]" のように範囲指定がおかしい時にはerror_rangeがthrowされる。

定義済み文字クラス
\d

[0-9]に同じ。[\d!"#$%&'()] のように文字クラス内([]の中)でも使用可能。

\D

[^0-9]に同じ。\d同様[]内でも使用可能。

\s

[ \t\n\v\f\r\u00a0\u1680\u2000-\u200a\u2028-\u2029\u202f\u205f\u3000\ufeff]に同じ。\d同様[]内でも使用可能。

※厳密にはWhiteSpaceとLineTerminatorに一致。今後Unicodeカテゴリの"Zs"に新たな文字が追加されることがあれば、その都度WhiteSpaceの右辺値は増える。

\S

[^ \t\n\v\f\r\u00a0\u1680\u2000-\u200a\u2028-\u2029\u202f\u205f\u3000\ufeff]に同じ。\d同様[]内でも使用可能。

\w

[0-9A-Za-z_]に同じ。\d同様[]内でも使用可能。

\W

[^0-9A-Za-z_]に同じ。\d同様[]内でも使用可能。

\p{...}

...の部分で指定されたUnicode property値を持つ文字にマッチ。\d同様[]内でも使用可能。例えば \p{scx=Hiragana} はUnicodeに存在するあらゆるひらがなにマッチする。

(SRELL 2.000以降で使用可能。ES2018/ES9.0でRegExpに導入された表現)

\P{...}

...の部分で指定されたUnicode property値を持たぬ文字にマッチ。\d同様[]内でも使用可能。

(SRELL 2.000以降で使用可能。ES2018/ES9.0でRegExpに導入された表現)

量指定子(回数指定)
*
*?

直前の文字を0回以上繰り返す。*は最長一致を優先、*?は最短一致を優先する。

先行する文字なしにいきなり回数指定が現れた時にはerror_badrepeatがthrowされる。以下5つも同じ。

+
+?

直前の文字を1回以上繰り返す。+は最長一致を優先、+?は最短一致を優先する。

?
??

直前に文字を0回ないし1回繰り返す。?は最長一致を優先、??は最短一致を優先する。

{n}

直前の文字をきっちりn回繰り返す。

'{' と '}' とが非対称な時にはerror_braceがthrowされる。以下2つも同じ。

{n,}
{n,}?

直前の文字をn回以上繰り返す。{n,}は最長一致を優先、{n,}?は最短一致を優先する。

{n,m}
{n,m}?

直前の文字をn回以上・m回以下繰り返す。{n,m}は最長一致を優先、{n,m}?は最短一致を優先する。

{3,2} のように範囲指定がおかしい時にはerror_badbraceがthrowされる。

括弧・後方参照・グループ化
(...)

文字列の捕獲。括弧には左から順に1, 2, 3...と番号が自動的に割り振られ、後でその番号により捕獲した文字列を参照できる。
'(' と ')' とが非対称な時にはerror_parenがthrowされる。

\n

後方参照。\の後ろに1-9で始まる十進数が続く時は、先に()で捕獲した文字列の参照と見なされる。対応する番号の括弧が先行していない時はerror_backrefがthrowされる。
例えば「(と|ト).\1」は、「とまと」や「トマト」にはマッチするが、「トマと」にはマッチしない。

(?<name>...)

文字列の捕獲を行う。捕獲した文字列は以後 \k<name> という表現で参照できる。また (...) 同様に番号も割り振られるので、前項の\nという表現でも参照できる。
例えば "(?<year>\d+)/(?<month>\d+)/(?<day>\d+)" という正規表現の場合、最初の括弧は\1という表現でも\k<year>という表現でも参照できる。

(SRELL 2.000以降で使用可能。ES2018/ES9.0でRegExpに導入された表現)

\k<name>

先にnameという名前で捕獲した文字列を参照する。該当する括弧が先行していなければerror_backrefがthrowされる。

(SRELL 2.000以降で使用可能。ES2018/ES9.0でRegExpに導入された表現)

(?:...)

グループ化。たとえば「白(?:い|く|かった)」は、「白い・白く・白かった」のいずれかにマッチするが、送り仮名の部分を後から参照することはできない。文字列の捕獲を行わぬかわりに照合処理が少し速くなる。

位置にマッチするもの
^

文字列の最初にマッチ。
multilineオプション指定時には、それに加えて文字列中のあらゆる改行の直後(行頭)にもマッチ。

$

文字列の最後にマッチ。
multilineオプション指定時には、それに加えて文字列中のあらゆる改行の直前にもマッチ。

\b

文字クラスの外側では\wと\Wとの境界にマッチ。
文字クラスの内側ではBEL (\u0008) にマッチ。

\B

文字クラスの外側では\bがマッチしないところにマッチ。
文字クラスの内側ではエスケープされた 'B' と解釈され、即ち 'B' そのものにマッチ。

(?=...)

肯定先読み。たとえば「白(?=い|く|かった)」は、後ろに「い・く・かった」のいずれかが続く「白」にマッチする。

(?!...)

否定先読み。たとえば「白(?!い|く|かった)」は、後ろに「い・く・かった」のいずれもが続かない「白」にマッチする(白黒、白鳥等)。

(?<=...)

肯定戻り読み。たとえば「(?<=あん|アン)パン」は、前に「あん・アン」のいずれかが先行する「パン」にマッチする。

※SRELL 1では、(...)内は固定幅の文字列のみ指定可能。固定幅でない時はerror_lookbehindがthrowされてきます。SRELL 2.000以降にはこのような制限はありません。

(?<!...)

否定戻り読み。たとえば「(?<!あん|アン)パン」は、前に「あん・アン」のいずれも先行しない「パン」にマッチする(餡パン、フライパン、シャンパン等)。

※SRELL 1では、(...)内は固定幅の文字列のみ指定可能。固定幅でない時はerror_lookbehindがthrowされてきます。SRELL 2.000以降にはこのような制限はありません。

std::regexとの相違点

Unicode対応のためのSRELL拡張

 Unicode対応のために、次のような型がSRELLには追加されています。

basic_regex

basic_regexのtypedef一覧
入力型 UTF-8用 UTF-16用 UTF-32用 備考
char u8cregex - - __cpp_char8_tマクロ非定義時はu8regexとしてもtypedefされる。
wchar_t - u16wregex u32wregex WCHAR_MAXが0xffff以上、0x10ffff未満ならu16wregexが定義され、WCHAR_MAXが0x10ffff以上ならu32wregexが定義される。排他的。
char8_t u8regex - - __cpp_char8_tマクロ定義時のみ。
char16_t - u16regex - SRELL_CPP11_CHAR1632_ENABLEDマクロ定義時のみ。
char32_t - - u32regex
//  UTF-8文字列用。
#if defined(__cpp_char8_t)
    typedef basic_regex<char8_t, u8regex_traits<char8_t> > u8regex;
#endif
#if defined(CHAR_BIT) && CHAR_BIT >= 8
    typedef basic_regex<char, u8regex_traits<char> > u8cregex;
    #if !defined(__cpp_char8_t)
        typedef u8cregex u8regex;
    #endif
#endif

#if defined(SRELL_CPP11_CHAR1632_ENABLED)
    //  C++11対応コンパイラ用。
    typedef basic_regex<char16_t, u16regex_traits<char16_t> > u16regex;
    typedef basic_regex<char32_t> u32regex;
#endif

//  wchar_tの大きさにより、u32wregex か u16wregex かにtypedefする。
#if defined(WCHAR_MAX)
    #if WCHAR_MAX >= 0x10ffff
        typedef wregex u32wregex;
    #elif WCHAR_MAX >= 0xffff
        typedef basic_regex<wchar_t, u16regex_traits<wchar_t> > u16wregex;
    #endif
#endif
				

 UTF-8やUTF-16に固有の処理をしているのはそれぞれ上記のu8regex_traitsu16regex_traitsです。このことを利用して、たとえば basic_regex<uint32_t, u16regex_traits<uint32_t> > のような「UTF-16文字列をuint32_t型の配列で処理する型」なるものを作ることもできます。

match_results

match_resultsのtypedef一覧
入力型 UTF-8用 UTF-16用 UTF-32用 備考
char u8ccmatch
u8csmatch
- - __cpp_char8_tマクロ非定義時はそれぞれu8[cs]matchにもtypedefされる。
wchar_t - u16wcmatch
u16wsmatch
u32wcmatch
u32wsmatch
WCHAR_MAXが0xffff以上、0x10ffff未満ならu16w[cs]matchが定義され、WCHAR_MAXが0x10ffff以上ならu32w[cs]matchが定義される。排他的。
char8_t u8cmatch
u8smatch
- - __cpp_char8_tマクロ定義時のみ。
char16_t - u16cmatch
u16smatch
- SRELL_CPP11_CHAR1632_ENABLEDマクロ定義時のみ。
char32_t - - u32cmatch
u32smatch
//  UTF-8文字列用。
#if defined(__cpp_char8_t)
    typedef match_results<const char8_t *> u8cmatch;
#endif
#if defined(__cpp_lib_char8_t)
    typedef match_results<std::u8string::const_iterator> u8smatch;
#endif
#if defined(CHAR_BIT) && CHAR_BIT >= 8
    typedef cmatch u8ccmatch;
    typedef smatch u8csmatch;
    #if !defined(__cpp_char8_t)
        typedef u8ccmatch u8cmatch;
    #endif
    #if !defined(__cpp_lib_char8_t)
        typedef u8csmatch u8smatch;
    #endif
#endif

#if defined(SRELL_CPP11_CHAR1632_ENABLED)
    //  C++11対応コンパイラ用。
    typedef match_results<const char16_t *> u16cmatch;
    typedef match_results<const char32_t *> u32cmatch;
    typedef match_results<std::u16string::const_iterator> u16smatch;
    typedef match_results<std::u32string::const_iterator> u32smatch;
#endif

//  wchar_tの大きさにより、w[cs]match を u32w[cs]match か u16w[cs]match かにtypedefする。
#if defined(WCHAR_MAX)
    #if WCHAR_MAX >= 0x10ffff
        typedef wcmatch u32wcmatch;
        typedef wsmatch u32wsmatch;
    #elif WCHAR_MAX >= 0xffff
        typedef wcmatch u16wcmatch;
        typedef wsmatch u16wsmatch;
    #endif
#endif
				

sub_match

sub_matchのtypedef一覧
入力型 UTF-8用 UTF-16用 UTF-32用 備考
char u8ccsub_match
u8cssub_match
- - __cpp_char8_tマクロ非定義時はそれぞれu8[cs]sub_matchにもtypedefされる。
wchar_t - u16wcsub_match
u16wssub_match
u32wcsub_match
u32wssub_match
WCHAR_MAXが0xffff以上、0x10ffff未満ならu16w[cs]sub_matchが定義され、WCHAR_MAXが0x10ffff以上ならu32w[cs]sub_matchが定義される。排他的。
char8_t u8csub_match
u8ssub_match
- - __cpp_char8_tマクロ定義時のみ。
char16_t - u16csub_match
u16ssub_match
- SRELL_CPP11_CHAR1632_ENABLEDマクロ定義時のみ。
char32_t - - u32csub_match
u32ssub_match
//  UTF-8文字列用。
#if defined(__cpp_char8_t)
    typedef sub_match_results<const char8_t *> u8csub_match;
#endif
#if !defined(__cpp_lib_char8_t)
    typedef sub_match_results<std::u8string::const_iterator> u8ssub_match;
#endif
#if defined(CHAR_BIT) && CHAR_BIT >= 8
    typedef csub_match u8ccsub_match;
    typedef ssub_match u8cssub_match;
    #if !defined(__cpp_char8_t)
        typedef u8ccsub_match u8csub_match;
    #endif
    #if !defined(__cpp_lib_char8_t)
        typedef u8cssub_match u8ssub_match;
    #endif
#endif

#if defined(SRELL_CPP11_CHAR1632_ENABLED)
    //  C++11対応コンパイラ用。
    typedef sub_match_results<const char16_t *> u16csub_match;
    typedef sub_match_results<const char32_t *> u32csub_match;
    typedef sub_match_results<std::u16string::const_iterator> u16ssub_match;
    typedef sub_match_results<std::u32string::const_iterator> u32ssub_match;
#endif

//  wchar_tの大きさにより、w[cs]submatch を u32w[cs]submatch か u16w[cs]submatch かにtypedefする。
#if defined(WCHAR_MAX)
    #if WCHAR_MAX >= 0x10ffff
        typedef wcsub_match u32wcsub_match;
        typedef wssub_match u32wssub_match;
    #elif WCHAR_MAX >= 0xffff
        typedef wcsub_match u16wcsub_match;
        typedef wssub_match u16wssub_match;
    #endif
#endif
				

 各prefixの意味するところは次の通りです。

  • u8: コンパイラがchar8_t型に対応しているかどうか(__cpp_char8_tマクロ定義の有無で判断)で次のように変わります。
    • char8_t対応なら:char8_t型配列またはstd::u8string型インスタンスをUTF-8文字列として扱う。
    • char8_t未対応なら:u8c-に同じ。
  • u16: char16_t型配列またはstd::u16string型インスタンスをUTF-16文字列として扱う。
  • u32: char32_t型配列またはstd::u32string型インスタンスをUTF-32文字列として扱う。
  • u8c: char型配列またはstd::string型インスタンスをUTF-8文字列として扱う(SRELL 2.100で導入。SRELL 2.002まではこれがu8-というprefixを使用していました)。
  • u16w: wchar_t型配列またはstd::wstring型インスタンスをUTF-16文字列として扱う(WCHAR_MAXが0xffff以上0x10ffff未満の時のみ定義)。
  • u32w: wchar_t型配列またはstd::wstring型インスタンスをUTF-32文字列として扱う(WCHAR_MAXが0x10ffff以上の時のみ定義)。

 このルールに基づいて、regex_iteratorやregex_token_iteratorでも同じようにu(8c?|16w?|32w?) prefixの付いた型がtypedefされています。

 基本的な使い方は次の通りです。

srell::u8regex u8re(u8"UTF-8文字列による正規表現");
srell::u8cmatch u8cm;
std::printf("%s\n", srell::regex_search(u8"検索対象となるUTF-8文字列", u8cm, u8re) ? "found!" : "not found...");

srell::u16regex u16re(u"UTF-16文字列による正規表現");
srell::u16cmatch u16cm;
std::printf("%s\n", srell::regex_search(u"検索対象となるUTF-16文字列", u16cm, u16re) ? "found!" : "not found...");

srell::u32regex u32re(U"UTF-32文字列による正規表現");
srell::u32cmatch u32cm;
std::printf("%s\n", srell::regex_search(U"検索対象となるUTF-32文字列", u32cm, u32re) ? "found!" : "not found...");

srell::u16wregex u16wre(L"UTF-16文字列による正規表現");
srell::u16wcmatch u16wcm;
std::printf("%s\n", srell::regex_search(L"検索対象となるUTF-16文字列", u16wcm, u16wre) ? "found!" : "not found...");
    //  上3行と下3行とは排他的。wchar_tが21ビット未満なら上、以上なら下。
srell::u32wregex u32wre(L"UTF-32文字列による正規表現");
srell::u32wcmatch u32wcm;
std::printf("%s\n", srell::regex_search(L"検索対象となるUTF-32文字列", u32wcm, u32wre) ? "found!" : "not found...");
			

 C++11より前のコンパイラでは、wchar_tが16ビット以上かつ21ビット未満の環境ならu8c-型とu16w-型とが、wchar_tが21ビット以上ある環境ならu8c-型とu32w-型とがそれぞれ利用可能です。もっともそのような環境でも、SRELLをincludeする前に次のようなコードを書いておけば、u8c-型、u16-型、u32-型を3つとも利用することができます。

typedef uint_least16_t char16_t;    //  16ビット以上ある符号無しの型をtypedefする。
typedef uint_least32_t char32_t;    //  32ビット以上ある符号無しの型をtypedefする。

namespace std
{
    typedef basic_string<char16_t> u16string;
    typedef basic_string<char32_t> u32string;
}

#define SRELL_CPP11_CHAR1632_ENABLED    //  手動でonにする。
			
名前付きキャプチャ用のSRELL拡張

 SRELL 2では名前付きキャプチャ (named-capture) 用に、次のメンバ函数がmatch_resultsクラスに追加されています。

difference_type length(const string_type &sub) const;
difference_type position(const string_type &sub) const;
string_type str(const string_type &sub) const;
const_reference operator[](const string_type &sub) const;
			

 使い方はstd::regexにある同名の函数と同じです。ただ引数として括弧の番号ではなく括弧の名前を渡すという点のみが異なります。

//  使用例。
srell::regex e("-(?<digits>\\d+)-");
srell::cmatch m;

if (srell::regex_search("1234-5678-90ab-cdef", m, e))
{
    const std::string by_number(m.str(1));      //  std::regexにもある括弧の番号を使ったアクセス。
    const std::string by_name(m.str("digits")); //  名前を使って同じ括弧へアクセス。SRELL 2の独自拡張。

    std::printf("results: bynumber=%s byname=%s\n", by_number.c_str(), by_name.c_str());
}
//  results: bynumber=5678 byname=5678 と表示される。
			
syntax_option_typeへのSRELL拡張

 SRELL 2には次のフラグオプションが追加されています。

namespace regex_constants
{
    static const syntax_option_type dotall; //  シングルライン指定。
        //  シングルラインモード用フラグオプションを参照。
}
			

 他のsyntax_option_type型の値と同様に、この値はbasic_regex内でも定義されています。

シングルラインモード指定用フラグオプション

 正規表現パターンのコンパイル時に dotall フラグオプション(regex_constants::syntax_option_type型またはbasic_regex::flag_type型)が指定されていると '.' の挙動が変わります
 このフラグオプションはPerl 5の //s に相当します。

正規表現エンジンとフラグ

 std::regexは、次の6つの正規表現エンジンから選択する仕組みになっています(既定はECMAScript)。

std::regexの正規表現エンジン
ECMAScript
(既定)

ECMAScriptの正規表現からUnicodeに依存した要素(\sの定義など)を取り除き、代わりにlocale依存の処理とC++の規格書29.13 [re.grammar] にある拡張とを追加したもの。

\uxxxxのような表現も使用可能ではあるものの、現在のlocaleの文字集合にない文字が指定された場合どうなるのかは不明。

basic

POSIXのbasic regular expression相当。

extended

POSIXのextended regular expression相当。

awk

POSIXのawk相当。

grep

POSIXのgrep相当。

egrep

POSIXのegrep相当。

 一方SRELLにはこのような選択肢はなく、常にECMAScript互換のエンジンで動きます。この関係で、std::regexで定義されているフラグオプションのうち次のものは、たとえ指定してもSRELLでは無視されます。

syntax_option_type(かつbasic_regexのflag_type)

  • icasemultiline以外のすべて

match_flag_type

  • match_any
  • format_sed

 std::regexのECMAScriptモードとSRELLとの違いは次の通りです。

  • std::regexのECMAScriptモード: ECMAScript 3rd editionのRegExpの仕様から「Unicode依存の定義」を取り去り、「localeへの依存」「[:クラス名:], [.クラス名.], [=クラス名=]という表現」を追加。
  • SRELL 2: ECMAScript 2018のRegExpの仕様に準拠。
  • SRELL 1: ECMAScript 2017のRegExpの仕様に「固定幅の戻り読み」を追加。

 同じECMAScriptベースでありながらもstd::regexとSRELLとはどちらもどちらの上位互換になっていません。

長考対策

 ECMAScriptの正規表現(およびその元となったPerlの正規表現)では通常、バックトラッキングと呼ばれる方法を使って照合が行われます。このバックトラッキング方式には、「量指定子(回数指定)が入れ子になっている正規表現」や「量指定子を伴う文字ないし文字クラスが連続していて、かつそれらが互いに排他的な集合になっていない正規表現」で検索を行うと、オートマトンが著しい長考に入ってしまうことがあるという問題が存在します。

 次のような例が有名です。

  • "aaaaaaaaaaaaaaaaaaaaaaaaaaaaa" =~ /(a*)*b/
  • "aaaaaaaaaaaaaaaaaaaaaaaaaaaaa" =~ /a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?aaaaaaaaaaaaaaaaaaaaaaaaaaaaa/

 残念ながらこの現象に対しては、あらゆる状況に適用できる根本的な解決策というものが見つかっていません。そこで制御が長時間返ってこなくなる事態を避けるため、SRELLは特定の位置からの照合が一定回数以上失敗すると、regex_error(regex_constants::error_complexity) を throw するようになっています。
 回数の既定値は16777216(256の3乗)ですが、regex_search()やregex_match()に渡すbasic_regex型インスタンスのlimit_counter変数に任意の値を代入することで変更することも出来ます。

非互換の変更

u8-prefixとu8c-prefix

 SRELL 2.002までu8-というprefixは、「char型の文字列をUTF-8文字列として扱うクラス型」であることを意味していました。しかしC++20よりchar8_t型が導入されることが決まり、今後u8-というprefixは標準ライブラリでもchar8_t型用に使われるようになってゆくことが予想されます。
 そこで標準ライブラリの命名規則との不整合を避けるため、SRELL 2.100以降では「char型の文字列をUTF-8文字列として扱う」ことを意味するprefixをu8-からu8c-に変更しました。

u8-からu8c-に変更されたクラス型一覧

  • basic_regex: u8cregex
  • match_results: u8ccmatch, u8csmatch
  • sub_match: u8ccsub_match, u8cssub_match
  • regex_iterator: u8ccregex_iterator, u8csregex_iterator
  • regex_token_iterator: u8ccregex_token_iterator, u8csregex_token_iterator

 一方u8-というprefixはSRELL 2.100以降、char8_t型と関連づけられるようになっています。ただしお使いのコンパイラがchar8_tに未対応である場合、後方互換のためu8-というprefix付きのクラスは、対応するu8c-付きクラスの単なるtypedefとして定義されるようになっています。

u8-とu8c-
PrefixSRELL 2.002までSRELL 2.100以降
char8_t未対応コンパイラchar8_t対応コンパイラ
u8- char型の文字列をUTF-8として扱う char8_t型の文字列をUTF-8として扱う
u8c- (Prefix自体未使用) char型の文字列をUTF-8として扱う

戻り読み

 SRELLのversion 1.xxxでは、ECMAScript 2017 (ES8) 規格書の21.2 RegExp (Regular Expression) Objects で定義されている正規表現に固定幅の戻り読み (lookbehind assertions) を加えたものが利用できました。

 ただ次のような経緯により、SRELL 1とSRELL 2とでは戻り読みの挙動に違いがあります。

 ECMAScript (JavaScript) の仕様を策定しているTC39はRegExpに追加する戻り読みについて、Perl5, Pythonなど多くのスクリプト言語が採用している固定幅限定の戻り読みではなく、制限のない可変幅の戻り読みを採用しました。
 一見すると後者は前者の上位互換のように思われますが、実のところこれらは次のような場合に異なる結果をもたらします。

"abcd" =~ /(?<=(.){2})./
//  固定幅の戻り読み: $& = "c", $1 = "b".
//  オートマトンは戻り読み内でも通常通り左から右へと文字列を照合してゆくため、
//  "c" の直前にある "b" が「$1によって最後にキャプチャされたもの」となる。

//  可変幅の戻り読み: $& = "c", $1 = "a".
//  オートマトンは戻り読み内では右から左へと走るため、"c" から2つ離れた
//  "a"が「$1によって最後にキャプチャされたもの」となる。
		

 SRELL 1が独自拡張として採用していたのは固定幅の戻り読みでした。対してSRELL 2には、RegExpの機能拡張に追随して可変幅の戻り読みが実装されています。このためSRELL 1 → SRELL 2では breaking change が発生しています。

 以下は外部のサイトです。