[C++]std::regexでパターンにマッチするすべての文字列を抽出する

std::regexを使う時多くの場合はstd::regex_searchを使うと思われますが、std::regex_searchはそのままだとマッチする1番最初の文字列しか得ることができません。
しかし、文字列の中からマッチする全ての要素が欲しいということはよくあることでしょう。調べても頑張ってずらしてもう一回searchだ!という方法しか出てきません。やはりスマートにやりたい・・・

std::match_results::suffix()

std::regex_searchの結果として得られるstd::match_resultssuffix()というメンバ関数を持っています。この関数は結果としてマッチした文字列を除いた残りの文字列(への参照)を返します。
その文字列に対してもう一度searchすれば2番目にマッチする部分文字列が得られ、それを繰り返せば残りのマッチング文字列を得ることができます。

#include <regex>

int main()
{
  std::string str = "1421, 34353, 7685, 12765, 976754";
  std::regex patern{R"(\d+)"};
  std::smatch match{};
  
  while (std::regex_search(str, match, patern)) {
    std::cout << match[0].str() << std::endl;
    str = match.suffix();
  }
}

[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ

とても簡単な方法ではありますが、一々コピーが発生するのと変更可能なstd::string等でしか使えないのが少し残念なところです。

std::sub_match::second

頑張ってずらしてもう一回searchだ!という方法を頑張らないでやる方法です。

std::match_resultsstd::sub_matchとしてパターン内各グループの結果を保持しており、一番最初のstd::sub_matchは見つかった文字列全体が得られます。
そして、std::sub_match::secondはそのサブマッチ文字列の次の位置を指すイテレータです。

つまり、一番最初のstd::sub_match::secondを始点としてもう一度std::regex_searchをすれば2番目にマッチする文字列が得られ、それを繰り返せば文字列内からパターンに一致する全ての部分文字列を抽出することができます。

#include <regex>

int main()
{
  constexpr char str[] = "1421, 34353, 7685, 12765, 976754";
  std::regex patern{R"(\d+)"};
  std::match_results<const char*> match{};
  
  for (bool ismatch = std::regex_search(str, match, patern); ismatch != false; ismatch = std::regex_search(match[0].second, match.suffix().second, match, patern)) {
    std::cout << match[0].str() << std::endl;
  }
}

[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ

for文の宣言部がとても長くて見づらいですが、std::regex_searchを繰り返し毎に実行し、その結果のbool値を見て終了判定しています。

そして、ループ中のstd::regex_searchmatch[0].secondから始めることでそれまでに一致した部分を飛ばして探索しています。なお、match.suffix().secondというのは元の文字列の終端(std::end(str)相当)に当たります(std::end(str)でも良いはずですが、型が合わないと怒られたのでこうしました・・・)。

この方法はイテレータを用いて元の文字列の参照範囲を変更して再検索しているだけなので、文字列のコピーは発生しません。

std::regex_iterator

上記std::sub_match::secondを用いる方法をラッピングしたイテレータstd::regex_iteratorとして標準に用意されています。

int main()
{
  constexpr char str[] = "1421, 34353, 7685, 12765, 976754";
  std::regex patern{R"(\d+)"};
  
  for (std::regex_iterator<const char*> itr{std::begin(str), std::end(str), patern}, last{}; itr != last; ++itr) {
    std::cout << (*itr).str() << std::endl;
  }
}

[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ

多少スッキリとし、基本的なイテレータ操作で書けているので処理も分かりやすいです。
先ほどのstd::sub_match::secondを使って次を検索、という部分をstd::regex_iteratorが中でよろしくやってくれています。

regex_searchesを作る

やはり、regex_searchのように一発でやりたいし、なんなら範囲for文使いたいです。なので綺麗にラッピングして少し便利にしてやりましょう。

template<typename Str>
auto regex_searches(Str& str, const std::regex& patern) {
  using std::begin;
  using std::end;
  using str_iterator_t = decltype(begin(str));
  
  struct wrap_regex_iterator {
    using iterator = std::regex_iterator<str_iterator_t>;
  
    auto begin() const noexcept -> iterator {
      return first;
    }
    
    auto end() const noexcept -> iterator {
      return last;
    }
    
    explicit operator bool() const noexcept {
      return first != last;
    }
    
    iterator first;
    iterator last;
  };
  
  return wrap_regex_iterator{{begin(str), end(str), patern}, {}};
}


int main() {
  const std::string str = "1421, 34353, 7685, 12765, 976754";
  std::regex patern{R"(\d+)"};
  
  for (auto&& match : regex_searches(str, patern)) {
    std::cout << match.str() << std::endl;
  }
}

[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ

ローカルクラス大好きなのでローカルクラスを使いましたが、外にあっても構いません。あとoperator boolはあくまでregex_searchのように戻り値を利用するために付けただけなのでなくてもいいです。というか範囲forで使う分にはほぼ無意味です。

入力となる型などを厳密にする場合は、std::regex_searchの各オーバーロードと同様にする必要がありますが、ここでは割愛・・・

書くことが多くなるので、数カ所で使うとかどうしても範囲forでーというときに多少便利になるかもしれません。

参考文献

謝辞

この記事の7割は以下の方によるご指摘によって成り立っています。

この記事のMarkdownソース