regexのUnicode対応とSRELL

目次

regexのUnicode対応に向けて

 現在のSRELLは「C++のregexがUTF-8, UTF-16, UTF-32にも対応していたらおそらくこんな感じではないか」あるいは「こんな感じであってほしい」というコンセプトの下に作られているのですが、C++20でchar8_tが導入されたことにより、templateの特殊化を利用すればUTF-8, UTF-16, UTF-32で処理をし分けるという方法が使えるようになったことを踏まえて、この辺りでSRELL相当の機能をregexにフィードバックすべく提案書をC++の委員会に出してみることにしました。

 委員会メンバーの方から聞いた話によりますと、ライブラリの拡張に携わっている人たちは現状のregexに不満を感じていて、「いっそのこと別の何かに置き換えてしまってregexはdeprecateしたい。P1433のCTRE(ソースコンパイル時に正規表現文字列から直接その表現専用の照合用コードを作ってしまうライブラリ。凄まじく速い。ただしランタイムにならないと正規表現文字列が判明しないような場合には使えない)がその『別の何か』になったら良いな」と考えている人もいるようです。
 一方Unicodeやテキスト処理の専門部会であるSG16は正規表現のことにそれほど関心がないようで、「Unicode対応を進めてゆく中で正規表現のこともいずれ何とかしたい。その場合はUTS #18を典拠に作業を進めたい」という考え方のように見受けられます。
 いずれにしましても現時点でregexを改良すべく動いている人はいないようですので、このままですとC++23はおろかC++26やC++29になってもregexの今のまま、ということになりかねません。

 現行の<regex>のクラスデザインを維持してUnicode対応を行う場合、「regexをUTF-32対応にして、UTF-8とUTF-16とについてはUTF-32に変換するイテレータのようなものを噛ませて使う」か「テンプレートの特殊化を使ってUTF-8, UTF-16, UTF-32を処理し分ける」か、それぐらいしか現実的な選択肢はないのではないかと個人的には考えているのですが、前者は既に却下されています。
 もし今回試してみる後者の方法もC++委員会のライブラリ拡張作業部会の関心を引かなかったとなりますと、既にregexは多くの委員から見捨てられてしまっていて「regexはもう良いから別の正規表現ライブラリの提案を誰か持ってきてくれ」という空気が支配的になっている可能性がいよいよもって濃厚になってきます。
 しかしregexの完全代替となりうるものは今のところまだ提案されていません。従いましてこの場合、C++規格にUnicode対応の正規表現ライブラリが含まれるようになるのは相当先のことになってしまうことが予想されます。

lookbehindの逆行限界を指定する方法

 SRELLのversion 2.300から2.500までには、lookbehindの逆行限界を指定するための手段としてmatch_lblim_availフラグとmatch_resultsクラスにBidirectionalIterator lookbehind_limitメンバとが追加されていました。しかしこのような指定は本来であれば、regex_search()に引数として渡すほうが自然です。
 にもかかわらずそうしなかったのは、引数の渡し方に次の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を昇順に並べたものです。「[lookbehind_limit, last) を検索対象範囲と見なし、その範囲内の first の位置から検索を開始する」という考え方に基づくとするなら、こちらのほうが直感的と言えるかもしれません。

 C++の委員会への提案書では「渡し方1」のほうを示してありますので、もし提案がこのまま受理されたりあるいは拒否されたりした場合には、SRELLにも渡し方1のオーバーロードを追加する予定です。ただもし議論の過程で渡し方2のほうが好ましいということになれば、SRELLにも2のオーバーロードを追加することになります。

 これら2つは引数の型の並びがまったく同じですのでコンパイラには区別がつきません。そのため委員会の好みを聞く前にどちらかの渡し方をSRELLに先行実装してしまいますと、後々非互換の仕様変更を行わなくてはならなくなってしまう可能性があります。
 そうなるのを避けるため、現時点ではmatch_lblim_availフラグとmatch_resultsクラスのBidirectionalIterator lookbehind_limitメンバとを使って指定するという方法を採っています。

※2020年 4月18日追記: C++委員会が今後<regex>に手を加える可能性はほぼゼロに近いと判断し、当初の予定通り「渡し方1」のほうをSRELLに実装しました(Version 2.600以降)。

WG21 ベルファスト会議

 2019年11月に開かれたWG21のベルファスト会議にて先述の「regexのUnicode対応」提案も少し議論されました。
 当初はLEWGI(ライブラリの追加拡張提案について議論する部会。従来はLWGが本選、LEWGが予選のような形だったが、近年提案が増えてLEWGだけでは捌ききれなくなってきたためかLEWGの下にLEWGIが設けられた)でも議論するための時間を確保してくださっていたのですが、提案者である自分が現地に行って直接プレゼンできないことと、SG16が「先にうちで議論したい」と述べたことなどから今回の会議ではSG16のみでの議論となりました。

 結果は概ね予想通りで、SG16としては「今さらregexを良くするために注力したくない。我々としてはCTRE (P1433) を補完するような形のライブラリが欲しい」とのことでした(註・CTREはランタイムにならないと正規表現パターンが確定しないケース、例えばgrepのようなツールやエディタの正規表現検索機能などには使えないので、その部分でregexの代わりとなるものが欲しいという意味)。
 ただP1433は2019年2月のKona会議で議論されたのを最後に改訂版が提出されていません。P1433は新規提案で今はまだLEWGIの段階にあり、この後LEWG、LWGと上がってゆく中で仕様の修正を求められることも予想されます。
 個人的には、現段階でCTREベースの標準ライブラリが出来ることを前提に今後の計画を立てるのは少し危なっかしいのではないかと感じています。

 ライブラリに対する提案の採否を決めるのはLWG-LEWG-LEWGIのラインですので、今回のSG16の判断が提案の行く末を直接左右するようなことはないと受け止めています。ただ今後、LEWG/LEWGIにおける判断材料の一つとして使われる可能性は否定できません。
 SG16は事実上「Unicode対応版のregexは要らない」と言った以上、この度のregex提案について再度議論することはないでしょうから、次なる焦点はLEWGIがどのような判断を下すかです。早ければ次回のプラハ会議(2月)で、時間がなければその次のヴァルナ会議(6月)で議題に上ると予想しています。

※2020年4月2日追記:2月のプラハ会議後にC++委員会の組織改編があったようで、現在では「ライブラリにまつわる新規提案はLEWGIでふるいに掛け、LEWG(Library Evolutionに改称予定?)で細部を詰め、LWGでは最終的な言い回しの調整をする」という仕組みになっているようです。また2020年6月に開催される予定でしたヴァルナ会議は無期限延期となっています。

WG21 プラハ会議

 2020年2月に開かれたWG21のプラハ会議にて「regexのUnicode対応提案」は事実上却下されました。これにより、C++の標準ライブラリにu8regex, u16regex, u32regexが入る可能性はなくなりました。

 発端となったのは、前記の予想に反してSG16が本提案を再度議論したことです。その際SG16メンバーの反応が総じて冷ややかだったことを受けてSG16のchairがLEWGIのchairとが話し合った結果、LEWGIのchairが「regexのUnicode対応提案」を議題から外してしまい、本提案はライブラリの作業部会で議論されることなく終わりを迎えたという次第です。
 この決定に対して異議を唱えた人もいなかったようですから、C++の委員会内には「std::regexを嫌う人たち」はいても「regexのUnicode対応を進めたい人」は一人もいなかったということに尽きるようです。

 とはいえ、実を言いますと「ろくに議論すらされない」というのは予想の範疇でした。提案者本人が直接現地に行ってプレゼンできないというのはその時点で相当不利だからです。
 予想外だったのは「std::regexを蛇蝎のごとく嫌う人たち」が勢い余って「std::regexをdeprecateにする提案」の準備を始めてしまったことです(20200402追記:文書番号としてP2124が確保されています)。現段階ではregexの代わりとなるものは存在しないばかりか提案すらされていませんから、これは実質的にC++から標準の正規表現ライブラリをなくすことを意味します。

 代替品のない状況でregexをdeprecateにしたところで誰も得をしないのですが、残念なことにC++委員会内、特にSG16周辺には「Clang, gcc, MSVCのいずれもstd::regexは性能が悪い。このような出来の悪いライブラリがC++規格に存在することが許せない」という考えの人が一定数いるようです。
 <regex>をdeprecateにする提案が実際に提出された時、ライブラリの作業部会はどのような対応をするのでしょうか。そして代替品すらないにもかかわらず、「性能が気に入らないから」という理由でdeprecateを提案するような人たちが委員をやっている作業部会に、新たな正規表現ライブラリの提案を持ち込む猛者は果たして現れるのでしょうか。
 ここから先は1ユーザとして行く末を傍観するつもりです。

※2020年 2月22日追記

 プラハ会議の議事録、SG16のchairからの説明、そしてstd::regexをdeprecateにする提案の草稿などを読んでいますと、どうも会議初日の午後、P2028 What is ABI, and What Should WG21 Do About It? が退けられてしまったことが「std::regexの性能を改善したり機能を追加したりするのは無理。もうどうにもならない」という空気を決定づけてしまったように見受けられます。

 regexというライブラリは「正規表現オブジェクトを保持する型 (basic_regex)」「basic_regexを受け取って検索や照合を行うアルゴリズム (regex_search, regex_match)」「結果を受け取る型 (match_results)」とに別れています。そのためもし仮に性能改善のためbasic_regexやmatch_resultsを根本的に作り直すようなことをしてしまいますと、改良前の<regex>をincludeしてコンパイルしたオブジェクトファイルと、改良後のものをincludeしてコンパイルしたオブジェクトファイルとをリンクした場合に、誤作動するバイナリが出来上がってしまいます。いわゆる ABI (Application Binary Interface) breaking による非互換問題の発生です。
 残念ながらこの種の非互換変更はリンク時に検出することが出来ません。regex_searchやregex_matchなどはbasic_regex型の参照を引数に取るということになっていますが、ABIレベルで見ると参照はポインタと同じ扱いですので、名前修飾の助けを借りてもリンカから見るとせいぜい「basic_regex型インスタンスの先頭アドレスを指している」というところまでしか分かりません。呼ぶ側呼ばれる側の双方で引数の数も型の種類も合っていれば、リンカは「問題なし」と判断してリンクしてしまいます。こちらとそちらでbasic_regex型の内部構造が変わっていてもリンカには知りようがないからです。

 元々ABIというのは実装側に付随する概念です。P2028の最初にも書かれている通り本来C++規格の外側にあるものですから、C++の委員会がとやかく言う性質のものではありません。しかしいつの頃からか各コンパイラとも自主的にABIを維持する傾向が強まってきて(例えばVisual Studio 2015以降、cl.exeのversionが14.xx、_MSC_VERが19xxとメジャー番号の部分が上がらなくなっているのもおそらくABI維持を示しているものと思われます)、今ではC++委員会においてさえも ABI breaking を引き起こす提案は問答無用で却下されるような空気が出来つつあります。
 このような現状を打破すべく、「C++23で一斉にABI breakingを行ってはどうか。それが出来ないならいっそのこともうABIの安定性を約束してしまってはどうか」と問い掛けたのが先のP2028でした。ところが委員の間で賛否が割れてしまい、「何らかの合意が形成されたとは言えない」という結論になってしまったようです。つまり「将来ABI breakする可能性は否定しないものの、当面は現状維持」です。

 P2028の中でstd::regexはunordered_mapと並んで、ABI breaking が可能なら速度の改善が図れるライブラリの筆頭にあげられています。もし初日にABI breakしようという流れになっていれば、前述のregexのUnicode対応提案ももう少し真剣に議論してもらえたのかもしれません。

SRELLの今後

 C++の委員会はもうstd::regexに関心がないということがよく分かりましたので、必要とあらば遠慮なく仕様を独自拡張してゆくつもりです。
 まずは前述のlookbehindの問題から取り組む予定です。2020年 4月18日追記: SRELL 2.600にて実装しました。