SRELL

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

目次

概要

std::regexと同じクラス構成のテンプレートライブラリ

 SRELLはECMAScript (JavaScript) 互換の正規表現エンジンを、"std::regex"(C++11で導入された正規表現ライブラリ)とクラス構成が同じになるようにラッピングしたものです。API/クラスデザインが同じですので、std::regexや、その基となったboost::regexと同じように扱えます。
 また、ヘッダファイルのみの純粋なテンプレートライブラリですのでincludeするだけですぐに使えます。事前のセットアップやインストールは不要です。

Unicodeに特化した実装

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

  • 特別な設定をせずとも既定でUTF-8/UTF-16/UTF-32文字列が扱えます。'.' がUTF-16文字列でサロゲートペアの片割れだけにマッチしたり、UTF-8文字列のコードユニットにマッチしたりするようなことがありません。
  • 文字クラス内でプレーン1以降の文字も [丈𠀋] のように指定できます。また [\u{1b000}-\u{1b0ff}] のような範囲指定もできます。
  • ギリシア文字のΣのように小文字が2種類ある字(u+03c2 [ς] と u+03c3 [σ])や、クロアチア語で使われるラテン文字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, Shift_JIS, EUC-JPの文字列はうまく扱えません。

Ignore case (icase) 検索にも配慮

 SRELLはicase検索(大文字・小文字を区別しない検索)時の速度低下が極力軽減されるようチューニングされています。

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

Download

使い方

 パスの通ったところに srell*.h*(srell.hpp, srell_ucfdata2.h, srell_updata3.h の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アーカイヴ内の readme_ja.txt も併せてご覧ください。

SRELLの正規表現

 ECMAScript仕様書の最新ドラフトのRegExpに定義されている表現が使えます。

 対応している表現の詳細は次の通りです(注記なきものはuモード/vモード共通)。

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

既定:改行文字(U+000A, U+000D, U+2028, U+2029の4文字)以外の文字にマッチ。即ち [^\u000A\u000D\u2028\u2029] 相当。
dotallフラグあり:あらゆる文字にマッチ。即ち [\0-\u{10ffff}] と等価。
dotall指定時には、.*が残りの文字列すべてにマッチしてしまうことに注意。

\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_escapethrowされてくる。

\\

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

\xHH

UTF-16におけるコードユニット値が、2桁の16進数HHである文字にマッチ。
\x の後ろに2桁の16進数が続いていない時はerror_escapethrowされてくる。

UTF-16において0x00-0xFFのコードユニット値はそれぞれU+0000~U+00FFの文字を表すので、この表現は事実上Unicodeのコードポイント値を表すとも言える。

\uHHHH

Unicodeのコードポイント値が、4桁の16進数HHHHである文字にマッチ。
\u の後ろに4桁の16進数が続いていない時はerror_escapethrowされてくる。

SRELL 2.500以降:連続する\uHHHHがUTF-16におけるサロゲートペアを構成している場合は、そのペアによって表されるUnicode値に変換される。例えば /\uD842\uDF9F//\u{20B9F}/ と解釈される。

\u{H...}

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

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

\

\^ $ . * + ? ( ) [ ] { } | / のうちのどれかが続いている時は、その続いている文字そのものを表す。すなわち\を前に置くとこれらの字が持つ特殊性が失われ、パターンコンパイラは文字通りに認識する('/' も含まれているのは、ECMAScriptでは正規表現を // で囲うためです)。
後述する文字クラス内では前記14字に加えて '-'"\-" の形で使える。

Note: ECMAScriptのuフラグモード、vフラグモードでは、「\とそれに続く何か」という組み合わせはすべて予約されています。そのため「\何か」に特別な意味がなければ、「何か」の部分の字そのものとして解釈されるだろうと期待することは出来ません。定義されていない「\何か」はエラーとなります。

^$.*+?()[]{}|\/
以外の文字

その文字そのものを表す。

選択
A|B

正規表現AまたはBにマッチ。/abc|def|ghi?|jkl?/ のように '|' はいくつでも並べることが出来る。
'|' によって区切られた各正規表現ブランチは左から右へと順番にマッチングが試みられ、最初にマッチングが成功したもののみが採用される。
たとえば "abcdef" に対して /abc|abcdef/ でマッチングを行った場合、結果は "abc" となる。

文字クラス
[]

文字クラス。文字集合。

  • [ABC]……ABCかにマッチ。
  • [^DEF]……最初が^の時は補集合。この例の場合DでもEでもFでもない文字にマッチ。
  • [G^H]……冒頭以外にある^^そのものを表す。この例の場合G^Hかにマッチ。
  • [I-K]……IJKかにマッチ。文字1-文字2という並びは「文字1のUnicodeにおけるコードポイント値から文字2の同コードポイント値までの範囲に含まれる文字のどれか」を意味する。
  • [-LM]……上記のような並び以外に位置する--そのものを表す。この例の場合-LMかにマッチ。
  • [N-P-R]……範囲指定直後の--そのものを表す。この例の場合はN, O, P, -, Rのいずれかにマッチ。Qは含まれず。
  • [S\-U]……'S''-''U'かにマッチ。\でエスケープされた'-''-'そのものを表す("\-"は文字クラス内でのみ使用可能)。
  • [.|({]…….|({かにマッチ。これらも文字クラスの中ではその特殊性を失う。
  • []……空集合。どの文字にもマッチせぬため、これが現れると照合は常にそこで失敗する。
  • [^]……空集合の補集合。どの文字にもマッチする。[\0-\u{10FFFF}]と同じ。

大文字・小文字を区別しない検索の時(icaseフラグ指定時) の挙動は次の通り。

  • [E-F]……'E' (u+0045) から'F' (u+0046) までの文字と、Unicodeのcase folding処理を適用した時にこの範囲の文字のどれかと同一視される字。即ち'E', 'F', 'e', 'f'の4文字にマッチする。
  • [E-f]……'E' (u+0045) から'f' (u+0066) までの文字と、Unicodeのcase folding処理を適用した時にこの範囲の文字のどれかと同一視される字。即ち'A'から'Z'までの26文字と、'a'から'z'までの26文字と、'[', '\', ']', '^', '_', '`', 'ſ', 'K'の7文字とにマッチする。

Perlの正規表現には「'[' の直後にある ']'']' そのものを表す」という特例があるが、ECMAScriptの正規表現にはそのような例外はない。従って ']' を文字クラスに含めるには常に '\' でエスケープして "\]" と書く必要がある。

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

vモード(unicodesetsフラグ指定時)においては、上記の機能 (union) に加えて次のような機能が文字クラスに追加される。

  • [\p{sc=Latin}&&\p{Ll}]……複数の文字集合の間に&&を書くと、両方の集合に含まれる文字からなる集合を作り出すことが出来る (Intersection)。この例の場合、ラテン文字 (\p{sc=Latin}) かつ小文字 (\p{Ll}) である文字のみにマッチ。
  • [\p{sc=Latin}--\p{Ll}]……複数の文字集合の間に--を書くと、左側の集合から右側の集合に含まれる文字を取り除くことが出来る (Difference/Subtraction)。この例の場合、ラテン文字 (\p{sc=Latin}) のうち小文字 (\p{Ll}) ではない文字のみにマッチ。
  • \q{...} という表現を使うことにより、文字クラスに文字列を含めることが出来る。例えば、[a-z\q{ch|th|ph}][a-z] の範囲にある1文字、または文字列 ch, th, ph のいずれかにマッチする。
    文字列が文字クラスに含まれる時は、長いもの、コードポイント数が多いものから順に照合が行われることが保証されている。従って先の例は事実上 (?:ch|th|ph|[a-z]) に等しい。
    \q{...} は演算の対象とすることも可能。
  • []を入れ子にすることが可能。そして上記演算子と共に使うことも出来る。例えば [\p{sc=Latin}--[a-z]] は、ラテン文字 (\p{sc=Latin}) のうち [a-z] の範囲にないものにマッチ。

同じ階層の [...] 内では、1種類の演算のみが使用可能(以下の例中A, B, C, Dなどは文字クラスを表すものとする)。

  • [AB--CD]……エラー。error_operatorthrowされる。AB でunionが行われた後に異なる種類の演算である -- が現れているため。
  • [[AB]--[CD]]: OK.
  • [A[B--C]D]: OK.
  • [\p{sc=Latin}--\p{Lu}--[a-z]]: OK. 同じ種類の演算を複数回行うのはエラーとならず。

注意事項1:vモードでは ( ) [ { } / - | の8文字を直接文字クラス内に書くことはできません。[]内に書く時は \ を直前に置いてエスケープする必要があります(]は従来からエスケープ必須)。
エスケープされずに現れた時は、error_noescapethrowされてきます。

注意事項2:次の18種類の二重記号は将来の機能拡張用に予約されています。これらを[]内に書くことは出来ません。使用された場合はerror_operatorthrowされてきます。

  • !!, ##, $$, %%, **, ++, ,,, ..,
  • ::, ;;, <<, ==, >>, ??, @@, ^^,
  • ``, ~~
定義済み文字クラス
\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同様に文字クラス内でも使用可能。

Note: 厳密には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値を持つ文字にマッチ。例えば \p{scx=Hiragana} はUnicodeに存在するあらゆるひらがなにマッチする。\d同様に文字クラス内でも使用可能。

vモードでは文字列プロパティー(properties of strings. 複数のコードポイントによって表現される文字列にマッチするUnicode property)にも対応。文字クラス内でも使えるが、補集合のクラス ([^...]) では使えない。使用した場合、error_complementthrowされる。

Note: ES2018/ES9.0で導入、SRELLは2.000で対応。
文字列プロパティー/properties of stringsはES2024で導入、SRELLは4.000で対応。

\P{...}

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

先の\pと異なり、vモードでも\P{...}は単一のコードポイントにマッチするUnicode propertiesだけに対応し、properties of stringsには対応しない。\P{...}...の部分に文字列プロパティー名を指定すると、error_complementthrowされる。

Note: ES2018/ES9.0で導入、SRELLは2.000で対応。
icase(case-insensitiveな、大文字小文字の区別をしない照合を行う)フラグが指定されている時、\P{...}はuモードとvモードとで異なる文字集合を表す場合があります。詳しくはこちらで解説しています。

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

直前の正規表現による照合を0回以上繰り返す。*は最長一致を優先、*?は最短一致を優先する。

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

+
+?

直前の正規表現による照合を1回以上繰り返す。+は最長一致を優先、+?は最短一致を優先する。

?
??

直前の正規表現による照合を0回ないし1回繰り返す。?は最長一致を優先、??は最短一致を優先する。

{n}

直前の正規表現による照合をきっちりn回繰り返す。

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

{n,}
{n,}?

直前の正規表現による照合をn回以上繰り返す。{n,}は最長一致を優先、{n,}?は最短一致を優先する。

{n,m}
{n,m}?

直前の正規表現による照合をn回以上・m回以下繰り返す。{n,m}は最長一致を優先、{n,m}?は最短一致を優先する。

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

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

正規表現のグループ化および文字列の捕獲/キャプチャ。正規表現全体において開き括弧 '(' が左のほうにあるものから順に、各括弧には1, 2, 3...と参照用の番号が自動的に割り振られ、括弧内の正規表現にマッチした文字列をその番号によって正規表現中の他の場所から参照できる。
'('')' とが非対称な時にはerror_parenthrowされる。

括弧自身またはその外側の正規表現に繰り返し指定がある場合、捕獲した文字列はループのたびに未定義値相当にクリアされる。そのためキャプチャした文字列を次のループに持ち越すことは出来ない。例えば /(?:(a)|(b))+/ は、\1\2かのどちらかは必ず空となる。

\N
(※Nは正の
整数)

後方参照。\の後ろに1-9で始まる十進数が続く時は、対応する番号の()で捕獲した文字列を使って照合が行われる。対応する番号の括弧が正規表現中に存在していない時はerror_backrefthrowされる。
例えば /(と|ト).\1/ は、「とまと」や「トマト」にはマッチするが、「トマと」にはマッチしない。

ECMAScriptの正規表現では、文字列を捕獲する括弧が対応する後方参照よりも先行している必要はない。そのため /\1(abc)//(abc\1)/ のような表現も有効でありエラーとはならない。

対応する括弧が何も捕獲していない時、後方参照は未定義値 (undefined) を参照しているものとされる。これは空文字相当として扱われ、照合は常に成功する。

(?<NAME>...)

名前付きの (...)。括弧内の正規表現とマッチした文字列は、括弧の番号に加えてNAMEというグループ名でも参照できるようになる。他は (...) に同じ。
例えば /(?<year>\d+)\/(?<month>\d+)\/(?<day>\d+)/ という正規表現の場合、最初の括弧は\1という表現でも\k<year>という表現でも参照できる。

'|' で区切られた位置なら、/(?<year>\d{4})-\d{1,2}|\d{1,2}-(?<year>\d{4})/ のように同じグループ名を複数回使うことも可能。

Note: ES2018/ES9.0で導入、SRELLは2.000で対応。同じ名前を複数回使える機能 (duplicate named capturing groups) はES2025で導入予定、SRELLは4.043で対応。

\k<NAME>

NAMEという名前の括弧によって捕獲された文字列を参照する。該当する括弧が正規表現中に存在していなければerror_backrefthrowされる。

Note: ES2018/ES9.0で導入、SRELLは2.000で対応。

(?:...)

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

位置にマッチするもの
^

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

$

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

\b

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

\B

文字クラスの外側では\bがマッチしないところにマッチ。
文字クラスの内側で使うとerror_escapethrowされる。

(?=...)

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

(?!...)

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

(?<=...)

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

Note: SRELL 1では、(...)内は/(?<=abc|def)//(?<=\d{2})/のような固定幅の文字列にマッチする表現のみ指定可能です。固定幅でない時はerror_lookbehindthrowされてきます。SRELL 2.000以降にはこのような制限はありません。

(?<!...)

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

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

フラグ変更
(?ims-ims:...)

同じ括弧内の...部分のみオプションフラグの状態を局所的に変更する。

  • (?i:...) 同じ括弧内のみicaseフラグをオンにする。
  • (?m:...) 同じ括弧内のみmultilineフラグをオンにする。
  • (?s:...) 同じ括弧内のみdotallフラグをオンにする。
  • (?-i:...) 同じ括弧内のみicaseフラグをオフにする。
  • (?-m:...) 同じ括弧内のみmultilineフラグをオフにする。
  • (?-s:...) 同じ括弧内のみdotallフラグをオフにする。

これらは(?ims:), (?im-s:)のようにして一度に複数指定することも可能。
ただし(?ii:), (?i-i:)のように同じ括弧内で同じフラグを複数回指定しようとすると、error_modifierthrowされる。

Note: ES2025で導入予定、SRELLは4.045で実装(ただし既定では無効)、4.058より既定で有効。

(?ims-ims)

正規表現内でフラグ指定をする。(?i)icaseフラグ、(?m)multilineフラグ、(?s)dotallフラグを指定した場合とそれぞれ同じ振る舞いをする。
これらは(?ims)のように一度に複数の指定をすることも可能。
また(?-m)のように-が先行している場合は、対応するフラグが無効化される。

この表現は正規表現の先頭でのみ使用可能(Python 3.11と同じ)。もし他の場所で使われたらerror_modifierthrowされる。

註:SRELL 4.007以降で利用可能。なおこの機能はSRELLの独自拡張で、ECMAScriptの仕様にはありません。この機能は#define SRELL_NO_UBMODを定義することにより、無効にできます。

std::regexへの拡張

Unicode対応

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

基本3クラス (basic_regex, match_results, sub_match) のtypedef一覧
Prefixと文字列の解釈 特殊化に使われる型 basic_regex
(-regex)
match_results
(-cmatch)
(-smatch)
sub_match
(-csub_match)
(-ssub_match)
備考
u8-
(UTF-8)
char8_t
または
char
u8regex u8cmatch
u8smatch
u8csub_match
u8ssub_match
コンパイラがchar8_t型に対応している時のみchar8_tが使われる。それ以外は後述するu8c-型の単なる別名 (typedef) となる。
u16-
(UTF-16)
char16_t u16regex u16cmatch
u16smatch
u16csub_match
u16ssub_match
コンパイラがchar16_t型、char32_t型に対応している時のみ定義される。
u32-
(UTF-32)
char32_t u32regex u32cmatch
u32smatch
u32csub_match
u32ssub_match
u8c-
(UTF-8)
char u8cregex u8ccmatch
u8csmatch
u8ccsub_match
u8cssub_match
u16w-
(UTF-16)
wchar_t u16wregex u16wcmatch
u16wsmatch
u16wcsub_match
u16wssub_match
WCHAR_MAX0xFFFF以上、0x10FFFF未満の場合のみ。
u32w-
(UTF-32)
u32wregex u32wcmatch
u32wsmatch
u32wcsub_match
u32wssub_match
WCHAR_MAX0x10FFFF以上の場合のみ。
u1632w- u1632wregex u1632wcmatch
u1632wsmatch
u1632wcsub_match
u1632wssub_match
WCHAR_MAXの値によって、上記 u16w- または u32w- の別名となる。

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

  • u8: コンパイラがchar8_t型に対応しているかどうか(__cpp_char8_tまたはSRELL_CPP20_CHAR8_ENABLEDマクロ定義の有無で判断)で次のように変わります。
    • char8_t対応なら:char8_t型配列またはstd::u8string型インスタンスをUTF-8文字列として扱う。
    • char8_t未対応なら:後述のu8c-に同じ。単なる別名としてtypedefされる。
    C++20ではUTF-8の文字列リテラル (u8"...") の型がcharからchar8_tに変更されましたが、上記の切替によりSRELLのu8-型は常にu8"..."に適するようになっています。
  • u16: char16_t型配列またはstd::u16string型インスタンスをUTF-16文字列として扱う。 UTF-16の文字列リテラル (u"...") に適しています。
  • u32: char32_t型配列またはstd::u32string型インスタンスをUTF-32文字列として扱う。 UTF-32の文字列リテラル (U"...") に適しています。
  • u8c: char型配列またはstd::string型インスタンスをUTF-8文字列として扱う(SRELL 2.100で導入。SRELL 2.002まではこれがu8-というprefixを使用していました)。
  • u16w: wchar_t型配列またはstd::wstring型インスタンスをUTF-16文字列として扱う(WCHAR_MAX0xFFFF以上0x10FFFF未満の時のみ定義される)。
  • u32w: wchar_t型配列またはstd::wstring型インスタンスをUTF-32文字列として扱う(WCHAR_MAX0x10FFFF以上の時のみ定義)。
  • u1632w: WCHAR_MAX0xFFFF以上0x10FFFF未満なら上記u16wに同じ。WCHAR_MAX0x10FFFF以上なら上記u32wに同じ。 上記u16w-, u32w-と異なり、このu1632w-型はWCHAR_MAX0xFFFF以上なら常に定義される。 SRELL 2.930以降で利用可能。

u16w-型とu32w-型とはWCHAR_MAXの大きさによって排他的に定義されます。そのせいでソースコードのポータビリティーに問題が発生しうるということに後になって気づいたため、SRELL 2.930でu1632w-が導入されました。

 先の表では省略しましたが、このルールに基づいてregex_iterator, regex_iterator2, regex_token_iteratorでも同じようにu(8c?|16w?|32w?|1632w) prefixの付いた型がtypedefされています。

 Unicode対応版の基本的な使い方は次の通りです。

srell::u8regex u8re(u8"UTF-8文字列による正規表現");
srell::u8cmatch u8cm;   //  検索対象がbasic_string型なら-smatch。以下同様。
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::u1632wregex u1632wre(L"UTF-16またはUTF-32の文字列による正規表現");
srell::u1632wcmatch u1632wcm;
std::printf("%s\n", srell::regex_search(L"検索対象となるUTF-16またはUTF-32の文字列", u1632wcm, u1632wre) ? "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...");
			
syntax_option_type

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

namespace regex_constants
{
    static const syntax_option_type dotall;  //  (SRELL 2.000以降)
        //  シングルライン指定。'.' の挙動を変える。ECMAScript 2018 (ES9) 以降のsフラグ (/.../s) に相当。

    static const syntax_option_type unicodesets;  //  (SRELL 4.000以降)
        //  vモードを使用する。

    static const syntax_option_type sticky;  //  (SRELL 4.049以降)
        //  このフラグを指定して作られた正規表現インスタンスは、検索時にmatch_continuous
        //  暗黙のうちにセットされる。ECMAScriptのyフラグ (/.../y) に相当。
        //  検索対象文字列 [begin, end) のうち begin の位置からしか照合が試みられぬので、
        //  regex_iterator, regex_iterator2, regex_token_iterator などでは使えぬことに注意。
}
			

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

 stickyフラグのメリットは、regex_search()match_continuousフラグを渡した時(またはregex_match()使用時)には利用されず無駄になる最適化処理がコンパイル時に飛ばされることです。そのためパターンコンパイル処理が通常より早く終わることが期待できます。

error_type

 次のerror_type値が追加されています。

namespace regex_constants
{
    static const error_type error_utf8; //  (SRELL 2.630以降)
        //  basic_regexに渡された正規表現中に、不正なUTF-8のシークウェンスが見つかった。

    static const error_type error_property; //  (SRELL 3.010以降)
        //  \p{...}または\P{...}で、対応していないUnicodeプロパティー名または値が指定された。

    static const error_type error_noescape; //  (SRELL 4.000以降。vモードのみ)
        //  文字クラス内で ( ) [ ] { } / - \ |\ を前置してエスケープする必要あり。

    static const error_type error_operator; //  (SRELL 4.000以降。vモードのみ)
        //  文字クラスの演算エラー。
        //  予約されている二重記号を使用した。または同じ階層で異なる演算をした。

    static const error_type error_complement; //  (SRELL 4.000以降。vモードのみ)
        //  文字列の補集合を使おうとした。
        //  \P{POSName}[^\p{POSName}][^\q{strings}]POSNameは文字列プロパティー名)が見つかった。

    static const error_type error_modifier; //  (SRELL 4.007以降)
        //  埋込フラグの (?ims-ims) 形式を先頭以外で使った。または一つの括弧内で同じフラグを複数回指定した。
			
例外なしモード

 SRELL 4.034以降では #define SRELL_NO_THROW を定義すると、エラー発生時にregex_error型の例外をthrowしなくなります。
 このモードにおいては、コンパイル時に発生したエラーはbasic_regex::ecode()によって、検索時に発生したエラーはmatch_results::ecode()によって、それぞれ読み出すことが出来ます。

 エラー発生時には、アルゴリズム函数 (regex_search(), regex_match()) は false を返します。

 なお #define SRELL_NO_THROW を定義しても、SRELL内部で使用しているstd::vectorstd::bad_allocを投げることまでは抑止できません。

 SRELL 2.600以降、引数としてBidirectionalIteratorを3つ取るオーヴァーロードが追加されています。

template <class BidirectionalIterator, class Allocator, class charT, class traits>
bool regex_search(
    BidirectionalIterator first,
    BidirectionalIterator last,
    BidirectionalIterator lookbehind_limit,
    match_results<BidirectionalIterator, Allocator> &m,
    const basic_regex<charT, traits> &e,
    const regex_constants::match_flag_type flags = regex_constants::match_default);

template <class BidirectionalIterator, class charT, class traits>
bool regex_search(
    BidirectionalIterator first,
    BidirectionalIterator last,
    BidirectionalIterator lookbehind_limit,
    const basic_regex<charT, traits> &e,
    const regex_constants::match_flag_type flags = regex_constants::match_default);
			

 3つ目のイテレータであるlookbehind_limitは、戻り読み (lookbehind) が行われる際の「逆行して良い限界位置」を指定するためのものです。
 違う言い方をしますと、この3イテレータ版では「検索対象範囲 [lookbeind_limit, last) の途中、firstから検索を始める」という処理が行われます。

const char text[] = "0123456789abcdefghijklmnopqrstuvwxyz";
const char* const begin = text;
const char* const end = text + std::strlen(text);
const char* const first = text + 10;    //  'a' の位置に合わせる。
const srell::regex re("(?<=^\\d+).");
srell::cmatch match;

std::printf("matched %d\n", srell::regex_search(first, end, match, re));
    //  戻り読みも [first, end) の範囲でのみ行われるのでマッチしない。

std::printf("matched %d\n", srell::regex_search(first, end, begin, match, re));
    //  beginまで逆行できるのでマッチする。
    //  即ち3イテレータ版は、[begin, end) というシークウェンスに対して
    //  firstよりsearchを始める。
			

 上の例にもあります通り3イテレータ版では、^first(第1引数として渡したほう)ではなく begin(第3引数として渡したほう)にマッチするようになります。

 また3イテレータ版使用時には、match_resultsposition()は第3引数として渡した位置からの距離を返すようになります。これに対してmatch_resultsprefix().firstには、第1引数として渡した位置がセットされます。

※3イテレータ版追加に伴い、SRELL 2.300~2.500で導入していた指定方法は廃止しました。

match_results

名前付きキャプチャ用のoverload函数

 SRELL 2.000以降では名前付きキャプチャ (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;

//  以下はSRELL 2.650以降。
difference_type length(const char_type *sub) const;
difference_type position(const char_type *sub) const;
string_type str(const char_type *sub) const;
const_reference operator[](const char_type *sub) const;
				

 使い方は<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の独自拡張。

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

Version 4.033まで:正規表現中に存在しないグループ名を渡した場合、error_backrefthrowされます。
Version 4.034以降:正規表現中に存在しないグループ名を渡した場合、matchedメンバがfalseになっているsub_match型インスタンスへの参照が返ってきます。

format()の書式文字列

 名前付きキャプチャ用に $<NAME> というシンボルが追加されています。下方の表も参照。

ecode() const

 直前に呼んだアルゴリズム函数内で発生したエラー値を返します。SRELL 4.034以降に実装されている「例外なしモード」用です。
 返されるエラー値は、regex_error::code()と同じerror_type型の整数です。直前のアルゴリズム函数呼び出しでエラーが発生していない時は0を返します。

//  std::regex互換のエラー処理。
srell::cmatch cm;

try {
    srell::regex re("a*");
    regex_search(text, cm, re);
} catch (const srell::regex_error &e) {
    //  エラー処理。
}
				
//  No throw/exceptionモードのエラー処理。
srell::smatch m;

srell::regex re("a*");
regex_search(text, m, re);
if (m.ecode()) //  非0ならエラーが発生した。
    //  エラー処理。
				
basic_regex

 SRELL 4.009以降、SRELLのbasic_regexクラスには次のメンバ函数が独自拡張して追加されています。

  • match(): srell::regex_match()と同じ処理を行う。
  • search(): srell::regex_search()と同じ処理を行う。

match() const

 srell::regex_match()と同じ処理を行います。basic_regex型インスタンスをreとした時、re.match(...)は、srell::regex_match(..., re, ...)のように書いたのと同じです。

 Overload函数の種類や引数の順番も、引数reが抜かされる点を除けばregex_match()のものと同じです。

template <typename BidirectionalIterator, typename Allocator>
bool match(
	const BidirectionalIterator begin,
	const BidirectionalIterator end,
	match_results<BidirectionalIterator, Allocator> &m,
	const regex_constants::match_flag_type flags = regex_constants::match_default) const;
//  srell::regex_match(begin, end, m, re, flags) に同じ。

template <typename BidirectionalIterator>
bool match(
	const BidirectionalIterator begin,
	const BidirectionalIterator end,
	const regex_constants::match_flag_type flags = regex_constants::match_default) const;
//  srell::regex_match(begin, end, re, flags) に同じ。

template <typename Allocator>
bool match(
	const charT *const str,
	match_results<const charT *, Allocator> &m,
	const regex_constants::match_flag_type flags = regex_constants::match_default) const;
//  srell::regex_match(str, re, flags) に同じ。

bool match(
	const charT *const str,
	const regex_constants::match_flag_type flags = regex_constants::match_default) const;
//  srell::regex_match(str, re, flags) に同じ。

template <typename ST, typename SA, typename MA>
bool match(
	const std::basic_string<charT, ST, SA> &s,
	match_results<typename std::basic_string<charT, ST, SA>::const_iterator, MA> &m,
	const regex_constants::match_flag_type flags = regex_constants::match_default) const;
//  srell::regex_match(s, m, re, flags) に同じ。

template <typename ST, typename SA>
bool match(
	const std::basic_string<charT, ST, SA> &s,
	const regex_constants::match_flag_type flags = regex_constants::match_default) const;
//  srell::regex_match(s, re, flags) に同じ。
				

 srell::regex_search()と同じ処理を行います。basic_regex型インスタンスをreとした時、re.search(...)は、srell::regex_search(..., re, ...)のように書いたのと同じです。

 Overload函数の種類や引数の順番も、引数reが抜かされる点を除けばregex_search()のものと同じです。

template <typename BidirectionalIterator, typename Allocator>
bool search(
	const BidirectionalIterator begin,
	const BidirectionalIterator end,
	match_results<BidirectionalIterator, Allocator> &m,
	const regex_constants::match_flag_type flags = regex_constants::match_default
) const;
//  srell::regex_search(begin, end, m, re, flags) に同じ。

template <typename BidirectionalIterator>
bool search(
	const BidirectionalIterator begin,
	const BidirectionalIterator end,
	const regex_constants::match_flag_type flags = regex_constants::match_default
) const;
//  srell::regex_search(begin, end, re, flags) に同じ。

template <typename Allocator>
bool search(
	const charT *const str,
	match_results<const charT *, Allocator> &m,
	const regex_constants::match_flag_type flags = regex_constants::match_default
) const;
//  srell::regex_search(str, m, re, flags) に同じ。

bool search(
	const charT *const str,
	const regex_constants::match_flag_type flags = regex_constants::match_default
) const;
//  srell::regex_search(str, re, flags) に同じ。

template <typename ST, typename SA, typename MA>
bool search(
	const std::basic_string<charT, ST, SA> &s,
	match_results<typename std::basic_string<charT, ST, SA>::const_iterator, MA> &m,
	const regex_constants::match_flag_type flags = regex_constants::match_default
) const;
//  srell::regex_search(s, m, re, flags) に同じ。

template <typename ST, typename SA>
bool search(
	const std::basic_string<charT, ST, SA> &s,
	const regex_constants::match_flag_type flags = regex_constants::match_default
) const;
//  srell::regex_search(s, re, flags) に同じ。

//  以下2つはstd::regex_search()にはありません。

template <typename BidirectionalIterator, typename Allocator>
bool search(
	const BidirectionalIterator begin,
	const BidirectionalIterator end,
	const BidirectionalIterator lookbehind_limit,
	match_results<BidirectionalIterator, Allocator> &m,
	const regex_constants::match_flag_type flags = regex_constants::match_default) const;
//  srell::regex_search(begin, end, lookbehind_limit, m, re, flags) に同じ。

template <typename BidirectionalIterator>
bool search(
	const BidirectionalIterator begin,
	const BidirectionalIterator end,
	const BidirectionalIterator lookbehind_limit,
	const regex_constants::match_flag_type flags = regex_constants::match_default
) const;
//  srell::regex_search(begin, end, lookbehind_limit, re, flags) に同じ。
				

ecode() const

 直前のパターンコンパイルで発生したエラー値を返します。SRELL 4.034以降に実装されている「例外なしモード」用です。
 返されるエラー値は、regex_error::code()と同じerror_type型の整数です。直前のコンパイルでエラーが発生していない時は0を返します。

//  std::regex互換のエラー処理。
try {
    srell::regex re("a{2,1}");
} catch (const srell::regex_error &e) {
    //  e.code() == srell::regex_constants::error_badbrace
}
				
//  No throw/exceptionモードのエラー処理。

srell::regex re("a{2,1}");
//  re.ecode() == srell::regex_constants::error_badbrace
				
regex_iterator2

 SRELL 4.013以降、regex_iterator2が追加されています。Grep, replace, splitなどをこれ1つでまかなえるように、regex_iteratorに次のような変更を加えたものです。

template <typename BidirectionalIterator,
    typename BasicRegex = basic_regex<typename std::iterator_traits<BidirectionalIterator>::value_type,
        regex_traits<typename std::iterator_traits<BidirectionalIterator>::value_type> >,
    typename MatchResults = match_results<BidirectionalIterator> >
class regex_iterator2;
			

 見にくくて分かりづらいかもしれませんが、テンプレート引数の2つ目はbasic_regexの型、3つ目はmatch_resultsの型です。regex_iteratorよりも単純化してあります。
 regex_iteratorに倣って次のtypedefが定義済みです。

typedef regex_iterator2<const char *> cregex_iterator2;
typedef regex_iterator2<const wchar_t *> wcregex_iterator2;
typedef regex_iterator2<std::string::const_iterator> sregex_iterator2;
typedef regex_iterator2<std::wstring::const_iterator> wsregex_iterator2;

//  charでUTF-8文字列を処理する。
typedef regex_iterator2<const char *, u8cregex> u8ccregex_iterator2;
typedef regex_iterator2<std::string::const_iterator, u8cregex> u8csregex_iterator2;

//  char16_t, char32_t利用可能時のみ定義。
typedef regex_iterator2<const char16_t *> u16cregex_iterator2;
typedef regex_iterator2<const char32_t *> u32cregex_iterator2;
typedef regex_iterator2<std::u16string::const_iterator> u16sregex_iterator2;
typedef regex_iterator2<std::u32string::const_iterator> u32sregex_iterator2;

//  char8_t利用可能時のみ定義。
typedef regex_iterator2<const char8_t *> u8cregex_iterator2;
//  std::u8string利用可能時のみ定義。
typedef regex_iterator2<std::u8string::const_iterator> u8sregex_iterator2;

//  char8_tがない時のみ定義。
typedef u8ccregex_iterator2 u8cregex_iterator2;
//  std::u8stringがない時のみ定義。
typedef u8csregex_iterator2 u8sregex_iterator2;

//  WCHAR_MAXが0x10FFFF以上の時のみ定義。
typedef wcregex_iterator2 u32wcregex_iterator2;
typedef wsregex_iterator2 u32wsregex_iterator2;
typedef u32wcregex_iterator2 u1632wcregex_iterator2;
typedef u32wsregex_iterator2 u1632wsregex_iterator2;

//  WCHAR_MAXが0xFFFF以上、10FFFF未満の時のみ定義。
typedef regex_iterator2<const wchar_t *, u16wregex> u16wcregex_iterator2;
typedef regex_iterator2<std::wstring::const_iterator, u16wregex> u16wsregex_iterator2;
typedef u16wcregex_iterator2 u1632wcregex_iterator2;
typedef u16wsregex_iterator2 u1632wsregex_iterator2;
			

done() const

 regex_iteratorと同じように、引数なしのコンストラクタでend-of-sequenceイテレータを作り、それとの比較がtrueになるまでforループでぐるぐる回すという方法も使えますが、もっと簡単な判定方法として「検索が終端まで行ったかどうかを示すdone()というメンバ変数」が用意されています。

srell::sregex_iterator2 eit;
srell::sregex_iterator2 it(text.begin(), text.end, re);

//  for (; it != eit; ++it) {   //  下と同じ。
for (; !it.done(); ++it) {
    //  何かする。
}
				

replace()

 コンストラクタに渡した範囲がstd::basic_string型インスタンスの一部であり、かつイテレータ作成後に余所でサイズ変更をしていない(メモリの割り当て位置が移動していない)場合は、イテレータのreplace()メンバ函数により、現在マッチしている箇所 ((*it)[0]) を別の文字列に置き換えることが出来ます。

 regex_iterator2::replace()std::basic_string型の文字列全体のインスタンスを第1引数、置換用文字列を第2引数として取ります。

//  [entire.begin(), entire.end()) 内の
//  [(*it)[0].first, (*it)[0].second) の範囲を
//  replacementないし[begin, end)に置換する。

template <typename ST, typename SA>
void replace(std::basic_string<char_type, ST, SA> &entire,
    const std::basic_string<char_type, ST, SA> &replacement);

template <typename ST, typename SA>
void replace(std::basic_string<char_type, ST, SA> &entire,
    BidirectionalIterator begin, BidirectionalIterator end);

template <typename ST, typename SA>
void replace(std::basic_string<char_type, ST, SA> &entire,
    const char_type *const replacement);
				

 置換により検索対象文字列が伸び縮みした場合はそれに合わせてイテレータ内部の位置情報を修正し、またメモリの再割り当てが発生した場合は位置情報を自動的に作り直します。

 regex_iterator2::replace()の使用例と、regex_iteratorとの違いとを示すサンプルプログラムです。

#include <cstdio>
#include <string>
#include <regex>
#include "srell.hpp"

template <typename Iterator, typename Regex>
void replace(const Regex &re, const std::string &text, const char *const title) {
    std::string::const_iterator prevend = text.begin();
    Iterator it(text.begin(), text.end(), re), eit;
    std::string out;

    for (; it != eit; ++it) {
        out += it->prefix();
        out += ".";
        prevend = (*it)[0].second;
    }

    const std::string::const_iterator end = text.end();
    out.append(prevend, end);
    std::printf("[%s] by %s\n", out.c_str(), title);
}

int main() {
    std::string text("a1b");
    std::regex re1("\\d*?");
    srell::regex re2("\\d*?");

    replace<std::sregex_iterator>(re1, text, "std::sregex_iterator");
    replace<srell::sregex_iterator>(re2, text, "srell::sregex_iterator");
    replace<srell::sregex_iterator2>(re2, text, "srell::sregex_iterator2");

    srell::sregex_iterator2 it(text, re2);
    for (; !it.done(); ++it)
        it.replace(text, ".");  //  replace()の使用。
    std::printf("[%s] by srell::sregex_iterator2::replace()\n", text.c_str());

    text = "a1b";  //  上で置換したので戻す。
    re2.replace(text, ".", true);
    std::printf("[%s] by srell::basic_regex::replace()\n", text.c_str());

    return 0;
}
---- 実行結果 ----
[.a...b.] by std::sregex_iterator
[.a...b.] by srell::sregex_iterator
[.a.1.b.] by srell::sregex_iterator2
[.a.1.b.] by srell::sregex_iterator2::replace()
[.a.1.b.] by srell::basic_regex::replace()
				

 regex_iteratorを使った置換では前述の特例により "1" の部分が置換されてしまっているのに対して、JavaScript互換の振る舞いをする下3つではそのまま残っています。

 ちなみにこのregex_iteratorの振る舞いは何に準拠したものなのか不明です。Perlの挙動とも異なっているようです。std::regexの元となったboost::regexは初期からずっとPerl指向ですので、ひょっとすると過去のPerlのいずれかの版に合わせた挙動なのかもしれません。

Split用補助函数

 イテレータがマッチした箇所のprefixと、最後にマッチした箇所より後ろの部分を集めるとsplit相当の処理となります(下図参照。itはイテレータ)。

(*it)[0]とit->prefix()とが指す位置
対象文字列 マッチ1箇所目 マッチ2箇所目
イテレータ it 1回目の
it->prefix()
(*it)[0] 2回目の
it->prefix()
(*it)[0] 2回目の
it->suffix()

 そこでsplitにも対応できるように、上図の青いところを集めやすくするためのヘルパ函数を用意しました。

  • bool split_ready(): 現在のit->prefix()がsplitされた文字列として有効な範囲を指しているか否かを返す。判断基準はECMAScriptのsplit()準拠 (it->prefix().first != (*it)[0].second)。
  • const typename value_type::value_type &remainder(bool only_after_match = false): 繰り返し終了後に残余区間を取り出す。上の表で言うところの「2回目のit->suffix()」の部分。
    イテレータitが一度もマッチしていない場合、it->suffix()は未定義値を返すのに対して、このit.remainder()は常に有効な範囲を返す。
    引数がtrueかつ前回のマッチが成功していたら、マッチした位置の終わり ((*it)[0].second) 以降を返す。それ以外は検索開始位置 (it->prefix().first) 以降を返す。

 もっとも単純なsplit処理は次の通りです。

for (; !it.done(); ++it) {
    if (it.split_ready())
        list.push_back(it->prefix());
}
list.push_back(it.remainder());
				

 他言語のsplitによくある「正規表現に括弧が含まれていた場合は、それがキャプチャしたものも追加する」処理や、分割回数 (LIMIT) を指定する機能も追加したい場合は次のように書きます。

for (std::size_t count = 0; !it.done(); ++it) {
    if (it.split_ready()) {
        if (++count == LIMIT)
            break;
        list.push_back(it->prefix());   //  *1
        for (std::size_t i = 1; i < it->size(); ++i) {
            if (++count == LIMIT) {
                list.push_back(it.remainder(true));
                //  上 (*1) でpush済みのprefix()区間を除外するため
                //  trueにする。
                return;
            }
            list.push_back((*it)[i]);
        }
    }
}
list.push_back(it.remainder());
				

 ヘルパーを使っても長くなってしまうので、さらなるヘルパ函数を用意しました。
 上のコードは次のように書けます。

std::size_t count = 0;
for (it.split_begin(); !it.done(); it.split_next()) {
    if (++count == LIMIT)
        break;
    list.push_back(it.split_range());
}
list.push_back(it.split_remainder());   //  remainder()ではなくsplit_remainder()であることに注意。
				
  • void split_begin(): split_ready()true を返す最初の位置まで移動する。このメンバ函数は最初(またはrewind()を呼んだ直後)に一回だけ呼ばれることを意図している。
  • bool split_next(): split_ready()true を返す次の位置まで移動する。戻り値はそのような位置に移動できたか否か。このメンバ函数は通常のoperator++()の代わりに使われることを意図している。
  • const typename value_type::value_type &split_range() const: イテレータが現在指し示している範囲を返す。
  • const typename value_type::value_type &split_remainder(): Iteratingが終わった後もしくは中断した後にこのメンバ函数を呼ぶと、最後にマッチした位置以降の範囲が返ってくる。
    上の remainder() とは異なり、only_after_match に相当するbool値は自動的に計算される。

 なおこのsplit対応はおまけです。「マッチする箇所を渡り歩く」というイテレータの本分と両立できない、もしくは難しいケースが発覚した場合には、split用のヘルパー函数は隠し機能としたり削除したりする可能性もあります。 SRELLに同梱されているupdataout3.cppで使うようになりましたので、削除は多分ありません。

 分割したものすべてに対して同じ処理をしたい時向けに、SRELL 4.049以降にはsplit_aptrange()が追加されています。これはdone()の戻り値に応じて、falseならsplit_range()を、trueならsplit_remainder()をそれぞれ呼び分けてくれるヘルパ函数です。

for (it.split_begin();; it.split_next()) {
    list.push_back(it.split_aptrange());    //  これは次のコードに同じ。
    //  list.push_back(!it.done() ? it.split_range() : it.split_remainder());

    if (it.done())
        break;
}
				
長考対策

 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変数に任意の値を代入することで変更することも出来ます。

std::regexとの相違点

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

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

<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互換のエンジンで動きます。この関係で、<regex>で定義されているフラグオプションのうち次のものは、たとえ指定してもSRELLでは無視されます。

syntax_option_type(かつbasic_regexflag_type

  • nosubs, optimize, collate, basic, extended, awk, grep, egrep(即ちicase, multiline以外のすべて)

match_flag_type

  • match_any, format_sed

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

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

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

簡略化

 余分なオーヴァーヘッドを避けるため、SRELLでは次のメンバ函数の実装が簡略化されています。

  • basic_regex::assign(): <regex>では例外がthrowされた場合(文字列からのコンパイルに失敗した場合)、*thisは元の値が維持されることになっていますが([re.regex.assign] の11)、SRELLでは*thisはclearされてしまいます。これはコンパイル開始時に元の内容を退避しないためです。
  • Version 4.033まで:match_results::operator[](size_type n): <regex>はn >= match_results::size()(つまり配列の範囲外アクセス)でも安全性を保証していますが([re.results.acc] の8)、SRELL 4.033まででは保証されません。安全性を保証するには、範囲外アクセスに備えるためだけにダミーのsub_match型メンバ変数が必要となるためです。

Tips

高速化

 同じ正規表現パターンによる検索を複数回行う時は、basic_regex型インスタンスの宣言時にstatic constを付け、パターンコンパイルが一回で済むようにすると高速化が期待できます。

//  プログラム中何度も呼ばれる函数。
bool is_included(const std::string &text, const std::string &regex)
{
    static const srell::regex r1("a*b");    //  OK. パターンコンパイルは最初の1回だけで済む。
//  srell::regex r2("a*b");     //  毎回パターンコンパイルが行われる。
    ...
			

 またループ内でアルゴリズム函数 (regex_search(), regex_match()) を複数回呼ぶ場合は、たとえ使わなくてもmatch_results型インスタンスを渡すようにすると高速化が期待できます。

std::vector lines;
srell::regex re("https?://\\S+");   //  URLっぽいものにマッチする正規表現。
srell::smatch match;    //  match_results<std::string::const_iterator> の typedef.

//  ここでlines内にテキストを読み込む。

for (std::size_t i = 0; i < lines.size(); ++i)
{
    //  regex_search()内で使い捨てのmatch_resultsが毎回用意されるので遅くなる。
//  if (srell::regex_search(lines[i], re))  //  *1
    if (srell::regex_search(lines[i], match, re))   //  *2
        ++count;
    ...
			

 速度が向上する理由は、match_results内には正規表現照合時に使用するスタックが存在するためです。*1のほうは函数が呼ばれるたびに毎回regex_search()内で使い捨てのmatch_resultsが用意されてスタック用のメモリ割り当て、解放が行われるのに対し、*2のほうは前回割り当てたメモリが次回以降もそのまま流用されますので、繰り返し回数が多い場合には*1に比べて倍以上速くなることもあります。

軽量化

 srell.hppをincludeする前に下記のマクロを定義しておくことで、使わない機能を切り離すことが出来ます。これにより出力されるバイナリのサイズを小さくし、またコンパイル時間(正規表現のではなくC++の)も短くすることが出来ます。

SRELL_NO_UNICODE_ICASE

大文字小文字を区別しない検索(icase検索)時に使用するUnicodeのcase foldingデータを切り離します。
icaseフラグが使用された時は、ASCIIの範囲のみcase-foldingが行われます([A-Z] → [a-z]のみ)。

SRELL_NO_UNICODE_PROPERTY

Unicodeプロパティー用のデータを切り離します。この場合\p{...}および\P{...}は使用できません。
また、名前付きキャプチャ用の名前も厳密にはチェックされず、'\''>' 以外なら何でも名前として受け付けるようになります。

このマクロが定義されると、後述するSRELL_NO_VMODEも暗黙的に定義されます。

SRELL_NO_UNICODE_DATA

SRELL_NO_UNICODE_ICASESRELL_NO_UNICODE_PROPERTYとが同時にdefineされます。

SRELL_NO_NAMEDCAPTURE

名前付きキャプチャ用のコードを切り離します。

SRELL_NO_VMODE

vモード用のコードを切り離します(SRELL 4.000~4.053のみ)。

SRELL_NO_UNICODE_POS

vモードのproperties of strings用データを切り離します。
SRELL 4.054以降は単独で使用可能ですが、SRELL 4.000~4.053では上のSRELL_NO_VMODEと併用する必要があります。

その他

不正なUTF-8

 速度を優先するため、初期のヴァージョンではSRELLに渡されたUTF-8文字列が適切なものであるかどうかのチェックが省略されています。詳細は次の通りです。

振る舞い
1.100-2.100

UTF-8として適正な並びであることが保証された文字列のみ渡されると想定しています。
1バイト目を見て2-4バイト文字であると分かれば、後続するデータは80~BFの範囲にあると決めつけた処理をしています。Trailing byteが実際に80..BFの範囲にあるかどうかのチェックをしていません。

そのため例えばバイナリの実行ファイルの中からUTF-8文字列を抜き出すような用途には対応していません。

2.200-2.710

Trailing byteが80..BFの範囲にあるかチェックします。加えて0x110000以上の値がデコードされないように先頭バイトが0xf4の時は2バイト目が80..8Fの範囲にあるかどうかイテレータ内でチェックします。

BMP文字の最短ではないUTF-8表現 (non-shortest form) についてはチェックしていません。そのため、例えば\u0030は最短の表現である0x30のみならず、0xc0 0xb0, 0xe0 0x80 0xb0, 0xf0 0x80 0x80 0xb0 にもマッチします。

2.720-4.047

SRELL 2.630以降、「コンパイル時に0x110000以上の値がデコードされてきたらerror_utf8throwする」ようになり、イテレータ側で範囲チェックする必要がなくなっていたので、先頭が0xf4の時の特別扱いを2.720で削除しました。

4.048-

最短のUTF-8表現のみ受け付けます。冗長な表現については、正規表現内から出てきたものはerror_utf8throwされ、検索対象文字列から出てきたものは無視されます(UTF-8の文字ではないもの扱い)。

SRELLとchar, wchar_t

 basic_regextypedefのうちUnicode用のprefix (u8-, u8c-, u16-, u16w-, u1632w-, u32-, u32w-) を持たぬ型は、入力文字列をUnicode値の連続として解釈します。

 例えばCHAR_BIT8である環境でsrell::regex (srell::basic_regex<char>) を使うと、入力文字列に現れる0x000xFFはそのままU+0000~U+00FFとして解釈されます。
 UnicodeのU+0000~U+00FFはISO-8859-1と同じですので、結果的にsrell::regexはISO-8859-1に対応しているとも言えます。

 またsrell::regexは、バイナリデータ中から特定のパターンを見つけ出すような場合にも使うことが出来ます。

 srell::wregex (srell::basic_regex<wchar_t>) も同じで、文字列中の0x00WCHAR_MAXまでがそのまま同じ値のUnicode値として解釈されます。

 WinAPIのW函数と組み合わせて使うのに適しているのは、UTF-16対応の srell::u16wregex または srell::u1632wregex です。srell::wregex は事実上UCS-2対応となってしまいます。

C++11以降の機能

 Feature test macroが適切に定義されないコンパイラ用に、SRELLのversion 4.056までは次のようなマクロが利用可能でした。

SRELL_CPP11_CHAR1632_ENABLED

char16_t, char32_tが使えるにもかかわらず__cpp_unicode_charactersを定義しないコンパイラ用です。
このマクロを定義しておくとSRELLはu16regex, u32regexなどをtypedefします。

SRELL_CPP11_INITIALIZER_LIST_ENABLED

Initializer listが使えるにもかかわらず__cpp_initializer_listsを定義しないコンパイラ用です。

SRELL_CPP11_MOVE_ENABLED

Moveできるにもかかわらず__cpp_rvalue_referencesを定義しないコンパイラ用です。

SRELL_CPP20_CHAR8_ENABLED

char8_tが使えるにもかかわらず__cpp_char8_t__cpp_lib_char8_tを定義しないコンパイラが用です。
このマクロの値が1の時、SRELLはchar8_tが利用可能と判断します。
このマクロの値が2の時はさらにstd::u8stringも利用可能と判断します。

 もしこれらSRELL_CPP*マクロに相当するものが引き続き必要でしたら、対応する__cpp_*マクロを直接定義してください。

C++98/03とUTF-8, UTF-16, UTF-32

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

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

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

#define __cpp_unicode_characters    //  手動でonにする。
// #define SRELL_CPP11_CHAR1632_ENABLED    //  SRELL 4.056まではこちら。
			

 ちなみにUTF-8やUTF-16に固有の処理をしているのは、basic_regexに対してテンプレート引数として渡されるu8regex_traitsu16regex_traitsです。このことを利用して、たとえば basic_regex<uint32_t, u16regex_traits<uint32_t> > のような「UTF-16文字列をuint32_t型の配列で処理する型」なるものを作ることもできます。

コンパイラ

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

  • GCC 9.1 (x86-64), 8.2 (ARM64, ARM)
  • Clang 8.0.0 (x86-64)
  • ICC (Intel C++ Compiler) 19.0.1 (x86-64)

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

非互換の変更

basic_regexの独自拡張

 SRELL 4.009~4.056までbasic_regexクラスには次のメンバ函数が独自拡張として存在していました。

replace() const

 regex_replace()は元の文字列には手を加えず、置換後の文字列は内部で新規に作ったものをreturnしてくるのに対して、このreplace()は元の文字列中の正規表現にマッチした箇所を、直接別の文字列に書き換えます。
 直接文字列を書き換える点以外は、ECMAScriptのString.prototype.split(RegExpオブジェクト, リミット)に準拠した動作をします。

 置換対象とできるのは、std::basic_string型またはこれと同じメンバを持つクラスです(4.010まではstd::basic_string型のみでした)。

 置換後の文字列の指定方法としては「1) 書式文字列を渡す」「2) コールバック函数を渡す」の2通りがあります。

書式文字列による置換
//  charT はbasic_regexの第1テンプレート引数の型。
template <typename StringLike>
void replace(
StringLike &s,
const charT *const fmt_begin,
const charT *const fmt_end,
const bool global = false) const;

template <typename StringLike>
void replace(
StringLike &s,
const charT *const fmt,
const bool global = false) const;

template <typename StringLike, typename FST, typename FSA>
void replace(
StringLike &s,
const std::basic_string<charT, FST, FSA> &fmt,
const bool global = false) const;
				

 global引数がfalseの時は、最初に正規表現にマッチした部分だけが置換されます。trueの時は、対象文字列中の正規表現にマッチする箇所すべてが置換の対象となります。

 書式文字列fmtの書式はsrell::regex_replace()のものと同じで、ECMAScript仕様書の Runtime Semantics: GetSubstitutionに準拠しています。
 次の表に掲載されている文字の並びは特殊な意味を持ち、それ以外のものはそのまま置換後の文字列になります。

書式文字列として使えるテキストシンボル一覧
テキストシンボル 置換テキスト
$$ $そのもの。
$& マッチした箇所全体。
$` マッチした箇所に先行する部分。
$' マッチした箇所より後方の部分。
$1 $2 $3 $4 $5 $6 $7 $8 $9
(後ろに数字が続かぬこと)
正規表現中の対応する括弧で捕獲された文字列。該当する括弧が何も捕獲していない場合は空文字に置換される。正規表現中のキャプチャ括弧の個数より大きな数が指定された場合は置換されず。
$nnnnは01から99までの範囲) 正規表現中の対応する括弧で捕獲された文字列。該当する括弧が何も捕獲していない場合は空文字に置換される。正規表現中のキャプチャ括弧の個数より大きな数が指定された場合は置換されず。
$<NAME>
  1. 正規表現中に「名前付きキャプチャ」が存在しない場合、置換されずそのまま出力される。
  2. 存在する場合、NAMEという名前の丸括弧によって捕獲された文字列と置換される。NAMEに相当する名前のグループ名が正規表現中にないか、あっても何もキャプチャされていなければ空文字列に置換される。
//  書式指定による置換例。
#include <cstdio>
#include <string>
#include "srell.hpp"

int main()
{
const srell::regex re("(\\d)(\\d)");    //  数字が2つ続いている箇所を探す。
std::string text("ab0123456789cd");

re.replace(text, "($2$1)", true);   //  $1と$2との順番を入れ替えて括弧でくくる。

std::printf("Result: %s\n", text.c_str());
return 0;
}
---- 実行結果 ----
Result: ab(10)(32)(54)(76)(98)cd
				
コールバック函数による置換

 replace()は書式文字列の代わりにコールバック函数を引数として受け取ると、正規表現にマッチする箇所を見つける度にその函数を呼び出します。

//  charT はbasic_regexの第1テンプレート引数の型。
template <typename StringLike, typename RandomAccessIterator, typename MA>
void replace(
StringLike &s,
bool (*repfunc)(
    std::basic_string<charT, typename StringLike::traits_type,
        typename StringLike::allocator_type> &replacement_text,
    const match_results<RandomAccessIterator, MA> &m,
    void *),
void *ptr = NULL) const;

template <typename MatchResults, typename StringLike>
void replace(
StringLike &s,
bool (*repfunc)(
    std::basic_string<charT, typename StringLike::traits_type,
        typename StringLike::allocator_type> &replacement_text,
    const MatchResults &m,
    void *),
void *ptr = NULL) const;
				

 コールバック函数 (repfunc) のシグネチュアは次の通りです。

//  charT はbasic_regexの第1テンプレート引数の型。
bool replacement_function(
std::basic_string<charT> &replacemen_text,
const match_results<const charT *> &m,  //  *cmatch用。
void *);

bool replacement_function(
std::basic_string<charT> &replacemen_text,
const match_results<typename std::basic_string<charT>::const_iterator> &m,  //  *smatch用。
void *);
				

 マッチした箇所の情報はmatch_results型インスタンスに詰め込まれ、コールバック函数には第2引数として渡されてきます。
 match_resultstypedefには、const charT *ベースの*cmatch系と、std::basic_string<charT>::const_iteratorベースの*smatch系とがありますので、コールバック函数のシグネチュアも2種類あります。

 第3引数は、replace()函数の第3引数がそのまま渡されてきます。コールバック函数に何か渡したい時に使います。

 コールバック函数側では、置換後の文字列を第1引数に詰めてからreturnします。戻り値としてtrueを返すと、引き続き正規表現にマッチする箇所が見つかるたびにコールバック函数が呼ばれ、falseを返すとそれ以降コールバック函数は呼ばれなくなります。

//  コールバック函数による置換例。
//  %エスケープされた文字列のデコード。
#include <cstdio>
#include <string>
#include "srell.hpp"

bool repfunc(std::string &out, const srell::u8cmatch &m, void *) {
out.push_back(std::strtoul(m[1].str().c_str(), NULL, 16));
return true;
}

int main() {
const srell::regex re("%([0-9A-Fa-f]{2})");
std::string c14("%E3%81%82%E3%81%84%E3%81%86%E3%81%88%E3%81%8A");
std::string c9803(c14), c11(c14);

re.replace(c9803, repfunc);  //  C++98/03

re.template replace<srell::smatch>(c11, [](std::string &out, const srell::smatch &m, void *) -> bool {  //  C++11
     out.push_back(std::strtoul(m[1].str().c_str(), NULL, 16));
     return true;
});

re.template replace<srell::smatch>(c14, [](auto &out, const auto &m, auto) -> bool {  //  C++14以降
     out.push_back(std::strtoul(m[1].str().c_str(), NULL, 16));
     return true;
});

std::printf("Result(C++98/03): %s\n", c9803.c_str());
std::printf("Result(C++11): %s\n", c11.c_str());
std::printf("Result(C++14-): %s\n", c14.c_str());
return 0;
}
---- 実行結果 ----
Result(C++98/03): あいうえお
Result(C++11): あいうえお
Result(C++14-): あいうえお
				

 コールバック函数へのポインタの代わりにラムダを使用する時は、ラムダの第2引数として受け取りたいmatch_results<RandomAccessIterator, Alloc>の型をreplace()のテンプレート実引数として明示的に指定する必要があります。match_resultsのテンプレート引数が推論できないためです。

補註
  • 4.012までは、*cmatch系を渡してほしい場合専用のcreplace()と、*smatch系を渡してほしい場合専用のsreplace()とがありましたが、煩雑化してしまったreplace()系を整理するため4.013で廃止しました。
  • 4.010までは 1) カスタムしたstd::basic_string<charT, ST, SA>型そのものと、2) コールバック函数に渡してほしいmatch_results<RandomAccessIterator, Alloc>との2つを、この順番で明示的に指定する必要がありました。

str_clip()

 これはbasic_regexのメンバではなくnamespace srellの直下にあるクラスですが、replace()で使うことを意図したものですのでここでご紹介します。

 SRELL 4.011以降、str_clip()というテンプレートクラスが追加されています。これはreplace()で検索、置換する範囲を制限するためのユーティリティーです。

//  str_clip()の使用例。
#include <cstdio>
#include <string>
#include "srell.hpp"

int main() {
const srell::regex re(".");
std::string text("0123456789ABCDEF");

srell::str_clip<std::string> ctext(text);
//  テンプレート実引数には、割り当てるbasic_string型インスタンスtextの型 (std::string) を指定。

//  pos & countペアでクリップ。4文字目から6文字分。
re.replace(ctext.clip(4, 6), "x", true);
std::printf("By pos&count: %s\n", text.c_str());  //  "0123xxxxxxABCDEF"

//  イテレータペアでクリップ。
re.replace(ctext.clip(text.begin() + 6, text.end() - 6), "y", true);
std::printf("By iterators: %s\n", text.c_str());  //  "0123xxyyyyABCDEF"

re.template replace<srell::smatch>(ctext.clip(6, 2), [](std::string &out, const srell::cmatch &, void *) {
    out = "Zz";
    return true;
});
std::printf("By lambda: %s\n", text.c_str());  //  "0123xxZzZzyyABCDEF"

return 0;
}
				

split() const

 split()は、検索対象文字列中の「正規表現とマッチする箇所」の前後を分割してゆき、その位置情報を収めたsub_match型インスタンスを、第1引数として渡された配列コンテナの参照にpushしてゆきます。

 次の点を除いて、ECMAScriptのString.prototype.split(RegExpオブジェクト, リミット)の仕様に準じた動作をします。

  • 分割個数 limit が指定されている場合、limit-1回まではECMAScriptの仕様書通りに振る舞い、残り1回となったら未処理の文字列をまとめてpushする。

 split()の分割個数の上限を指定する機能というのはよく見られるものですが、JavaScriptのそれは少し変わっていて、分割をlimit回行うとそれ以降のまだ調べていない部分の文字列はそのまま捨ててしまいます。この挙動は個人的にあまり嬉しくありませんので、上のような変更を加えました。

template <typename container, typename ST, typename SA>
void split(
container &c,
const std::basic_string<charT, ST, SA> &s,
const std::size_t limit = static_cast<std::size_t>(-1)) const;

//  以下2つは4.011で追加。
template <typename container, typename BidirectionalIterator>
void split(
container &c,
const BidirectionalIterator begin,  //  container::value_type::iteratorと同型もしくはキャスト可能。
const BidirectionalIterator end,
const std::size_t limit = static_cast<std::size_t>(-1)) const;

template <typename container>
void split(
container &c,
const charT *const str,
const std::size_t limit = static_cast<std::size_t>(-1)) const;
			

 結果を受け取るコンテナ型cは、メンバ函数としてpush_back()が実装されていれば何でも使えます。

 正規表現の中にキャプチャ括弧があった場合はそれらによって捕獲された文字列もpushされます。何も捕獲していない括弧のところも飛ばされず、空文字列がpushされます。

#include <cstdio>
#include <string>
#include <vector>
#include "srell.hpp"

template <typename Container>
void print(const Container &c) {
for (typename Container::size_type i = 0; i < c.size(); ++i)
    std::printf("%s\"%s\"", i == 0 ? "{ " : ", ", c[i].str().c_str());
std::puts(" }");
}

int main() {
std::string text("01:23:45");
srell::regex re(":");
std::vector<srell::csub_match> res;  //  またはsrell::ssub_match.

re.split(res, text);    //  無制限分割。
print(res);     //  { "01", "23", "45" }

res.clear();    //  split()内ではclear()されないので注意。
re.split(res, text, 2); //  2分割。
print(res);     //  { "01", "23:45" }
                //  JavaScriptの場合 { "01", "23" } になる。

re.assign("(?<=(\\d?)):(?=(\\d?))");  //  ':'の前後の文字を捕獲。
res.clear();
re.split(res, text);
print(res);     //  { "01", "1", "2", "23", "3", "4", "45" }

text.assign("caf\xC3\xA9");     //  "café"
re.assign("");

res.clear();
re.split(res, text);    //  char型の1文字単位で分割。
print(res);     //  { "c", "a", "f", "\xC3", "\xA9" }

srell::u8cregex u8re("");
res.clear();
u8re.split(res, text);  //  UTF-8の1文字単位で分割。
print(res);     //  { "c", "a", "f", "é" }

return 0;
}
			

match_lblim_availフラグとmatch_results.lookbehind_limitメンバ

 SRELL 2.300~2.500には次のような独自拡張がありました。

 match_lblim_availフラグがセットされると戻り読み (lookbehind) が行われる際、アルゴリズム函数に渡したmatch_results型インスタンスのlookbehind_limitメンバの値を「逆行できる限界」と見なすようになります。

const char text[] = "0123456789abcdefghijklmnopqrstuvwxyz";
const char* const begin = text;
const char* const end = text + std::strlen(text);
const char* const first = text + 10;    //  'a' の位置に合わせる。
const srell::regex re("(?<=^\\d+).");
srell::cmatch match;

match.lookbehind_limit = begin;

std::printf("matched %d\n", srell::regex_search(first, end, match, re));
    //  戻り読みも [first, end) の範囲でのみ行われるのでマッチしない。

std::printf("matched %d\n", srell::regex_search(first, end, match, re, srell::regex_constants::match_lblim_avail));
    //  match.lookbehind_limitまで逆行できるのでマッチする。
    //  即ち match_lblim_avail指定時は [match.lookbehind_limit, end) というシークウェンスに対して
    //  firstよりsearchを始める。
		

 上の例にもあります通りmatch_lblim_availが指定された時は、^first ではなく match.lookbehind_limit にマッチするようになります。

 SRELL 2.600以降では、regex_search()の引数としてlookbehindの逆行限界位置を指定できるようにしました。これに伴い上記の方法は廃止しました。
 この2.600で導入した「3イテレータ方式」を最初から採用しなかったのは、引数の渡し方に次の2通りが考えられたためです。

//  渡し方1
bool regex_search(
    BidirectionalIterator first,
    BidirectionalIterator last,
    BidirectionalIterator lookbehind_limit,
    match_results<BidirectionalIterator, Allocator>& m,
    const basic_regex<charT, traits>& e,
    regex_constants::match_flag_type flags = regex_constants::match_default);

//  渡し方2
bool regex_search(
    BidirectionalIterator lookbehind_limit,
    BidirectionalIterator first,
    BidirectionalIterator last,
    match_results<BidirectionalIterator, Allocator>& m,
    const basic_regex<charT, traits>& e,
    regex_constants::match_flag_type flags = regex_constants::match_default);
		

「渡し方1」はlookbehindの逆行限界を、通常の [first, last) への追加として渡すようにしたものです。「渡し方2」は、3つのiteratorを昇順に並べたものです。

 これら2つは引数の型の並びがまったく同じですのでコンパイラには区別がつきません。そのため、例えばSRELLは渡し方1を採用したのに、将来C++規格のregex_search()が渡し方2を使って拡張されたなどということが起こりますと、std::regexsrell::regexとの間に「コンパイル時にエラーとならない非互換」が発生するというかなり困った事態が発生してしまいます。
 これを直すためにSRELL側で引数の並び順をC++規格に合わせるような変更を加えますと、今度は「SRELLを新しいものに更新したら挙動が変わってしまった」という、これまた困った問題が発生してしまいます。

 折しもC++20でchar8_tが導入されたことにより、templateの特殊化を利用すればUTF-8, UTF-16, UTF-32で処理をし分けるという方法が使えるようになったタイミングでもありましたので、C++委員会に対して<regex>をUnicode対応にする提案を出してみて、委員会が<regex>の拡張を現実的な選択肢として考えているのか明らかにしてみようと思い立ちました。その結果が判明するまでの間、つなぎとして実装したのが前記のmatch_resultsに独自メンバを足すという方法です(お世辞にも綺麗な方法とは言えないので、C++規格が将来このような拡張をすることは考えにくく、この方法なら衝突の可能性がないと思われたため)。
 結果、C++委員会が今後<regex>に手を加える可能性はほぼゼロに近いと分かりましたので、SRELLのversion 2.600で「渡し方1」を実装した上でつなぎとして実装したものを削除し、今に至っています。

u8-prefixとu8c-prefix

 C++のversion問わずu8-というprefixは、u8"..." 文字列リテラルを直接扱えることを示しています。一方u8c-は、char型の文字列をUTF-8として扱うことを示しています。
 C++17までu8"..."const char[N]型でしたので、このような区別は必要ありませんでした。

 しかしC++20でchar8_t型が導入され、以後u8"..."const char8_t[N]型に変更されました。これによりSRELLでも新しい「char8_tを使ったUTF-8文字列」と、従来からある「charを使ったUTF-8文字列」とを区別する必要が出てきたため、UTF-8用のprefixも2種類必要になりました。

 本来なら後から出来た「char8_tを使ったUTF-8文字列」のほうに新しいprefixを用意すべきところなのでしょうが、既にstd::u8stringがそうであるように、今後u8-というprefixはC++規格のライブラリでもchar8_t型に特殊化されたクラス、アルゴリズムという意味で使われるようになってゆくことが予想されます。
 そこでC++標準ライブラリの命名規則との不整合を避けるため、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.nnnでは、ECMAScript 2017 (ES8) 規格書の21.2 RegExp (Regular Expression) Objects で定義されている正規表現に固定幅の戻り読み (lookbehind assertions) を加えたものが利用できました。

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

 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.000以降には、RegExpの機能拡張に追随して可変幅の戻り読みが実装されています。このためSRELL 1.401 → SRELL 2.000では breaking change が発生しています。

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

ECMAScript (JavaScript) のRegExp関連

提案書(2024/10/08更新)