C++11 の正規表現ライブラリの曖昧さ

概要

C++11 の正規表現ライブラリの仕様の曖昧な部分を指摘し、現実の実装のぶれを例示します。

Multiline

定義

ECMA-262 では、Multiline について以下のような定義が存在します。

§15.10.2.6 (ECMA-262)
Multiline が true の場合 ^$ は改行文字の直後や直前にもそれぞれマッチし、 false ならばマッチしない。(文字列の先頭と最後にはそれぞれ常にマッチする)
§15.10.4.1, §15.10.7.4 (ECMA-262)
新しく作られた正規表現オブジェクトのフラグに "m" が指定されている場合、 multiline プロパティは true, そうでなければ false。 (つまり、デフォルトでは false)

ところが C++11 n3485 には Multiline についての言及はありません。

したがって ECMA-262 のデフォルトの動作、つまり Multiline=false が C++11 で期待される動作ということになるので、 ^$ は改行文字の直後や直前にはそれぞれ決してマッチしないということになります。

現実の実装

Multiline=false
Multiline=true

問題点

Multiline=false であると解釈すると、以下のフラグの存在意義がなくなります。

§28.5.2 [re.matchflag] match_not_bol
^ が文字列の先頭にマッチしないようにする。
§28.5.2 [re.matchflag] match_not_eol
$ が文字列の最後にマッチしないようにする。

これらのフラグを使用した場合、 ^$ は決してマッチしない正規表現ということになり実用的な意味が失われます。

ところが Multiline=true であると解釈すれば、 改行文字を含む長い文章の一部を取り出した文字列に対してマッチを行うことを考えた場合に ^$ が改行の直後や直前にマッチするので、 行単位のマッチを行うことが可能になります。

たとえば regex_replace や regex_iterator などで改行を含む文章を処理する場合に役に立つことが予想されます。

まとめ

仕様策定時点では Multiline=true を想定していたのではないか、 という推測ができますが現在の仕様では false になってしまうので現実実装に混乱が生じています。 曖昧な仕様を明確にする必要があります。

参考文献

regex_match

定義

C++11 n3485 §28.11.2 [re.alg.match] p2 では regex_match について次のような定義がなされています。

Effects: Determines whether there is a match between the regular expression e, and all of the character sequence [first,last). The parameter flags is used to control how the expression is matched against the character sequence. Returns true if such a match exists, false otherwise.

この文章の前半は異なる解釈をすることが可能です。

  1. 正規表現 e で入力文字列を検索 (regex_search) する。その検索が成功した場合に、その検索結果が文字列全体と一致している場合は true を返す。
  2. 正規表現 e の最後にまるで \z が追加されているかのように動作する。(\z は Perl の正規表現で、文字列の最後のみにマッチする)

この解釈のぶれによって ECMAScript フラグで作成された正規表現に以下のような微妙な違いが生じます。

std::regex e("Get|GetValue");
regex_search を使用した場合
この正規表現は "GetValue" 全体にマッチすることはありません。 というのは、ECMAScript では左側から順番にマッチするかどうかを判定し、最初に見つかったマッチが採用されるからです。 したがって | の左側の "Get" がマッチするので、 右側の "GetValue" が考慮されることはありません。
1 の解釈の regex_match の場合
検索結果は regex_search と同じなので "Get" となります。"Get" は文字列全体ではないので、false を返します。
2 の解釈の regex_match の場合
正規表現 e の最後にまるで \z が追加されているかのように動作、 つまり e が (?:Get|GetValue)\z であるかのように動作するので その検索結果は "GetValue" となります。 マッチが成功したので true を返します。

現実の実装

1 の解釈
2 の解釈

まとめ

仕様が曖昧といえば曖昧であるものの、1 の解釈を行うのはかなり無理があるのではないかと思います。 この問題は C++ 標準化委員会に既に報告されています。

参考文献

付録