Я хотел построить представление обо всех подсовпадениях regex в text. Вот два способа определить такое представление:
char const text[] = "The IP addresses are: 192.168.0.25 and 127.0.0.1";
std::regex regex{R"((\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3}))"};
auto sub_matches_view =
std::ranges::subrange(
std::cregex_iterator{std::ranges::begin(text), std::ranges::end(text), regex},
std::cregex_iterator{}
) |
std::views::join;
auto sub_matches_sv_view =
std::ranges::subrange(
std::cregex_iterator{std::ranges::begin(text), std::ranges::end(text), regex},
std::cregex_iterator{}
) |
std::views::join |
std::views::transform([](std::csub_match const& sub_match) -> std::string_view { return {sub_match.first, sub_match.second}; });
sub_matches_view — std::csub_match. Он создается путем предварительного создания представления объектов std::cmatch (через итератор регулярных выражений), и, поскольку каждый std::cmatch представляет собой диапазон объектов std::csub_match, он выравнивается с помощью std::views::join.sub_matches_sv_view — std::string_view. Он идентичен sub_matches_view, за исключением того, что он также заключает каждый элемент sub_matches_view в std::string_view.Вот пример использования вышеуказанных диапазонов:
for(auto const& sub_match : sub_matches_view) {
std::cout << std::string_view{sub_match.first, sub_match.second} << std::endl; // #1
}
for(auto const& sv : sub_matches_sv_view) {
std::cout << sv << std::endl; // #2
}
Петля #1 работает без проблем - результаты распечатки правильные. Однако Цикл #2 вызывает проблемы с использованием кучи после освобождения в соответствии с Address Sanitizer. На самом деле, простое повторение sub_matches_sv_view без доступа к элементам также вызывает эту проблему. Здесь — это код в Compiler Explorer, а также выходные данные Address Sanitizer.
У меня нет идей относительно того, где моя ошибка. text и regex никогда не выходят за рамки, я не вижу никаких итераторов, к которым можно было бы получить доступ за пределами их жизни. Объект std::csub_match содержит итераторы (.first, .second) в text, поэтому я не думаю, что он должен оставаться живым после создания std::string_view в std::views::transform.
Я знаю, что есть много других способов перебирать совпадения регулярных выражений, но меня особенно интересует, что вызывает ошибки памяти в моей программе, мне не нужны обходные пути для этой проблемы.
@Barry Большое спасибо, я пытался несколько раз, но не смог скомпилировать его достаточно быстро (возможно, потому, что я использовал немного другие флаги). На моей машине это занимает около 30 секунд. Я отредактирую вашу ссылку в вопросе.
Кстати, в libc++ такой проблемы не было. godbolt.org/z/a7M3jd8ef
Некоторая ошибка в трансформации: godbolt.org/z/q4P1x5qTb без transform работает.
Я не думаю, что это баг transform_view, я думаю, проблема скорее в <regex>.
@MarekR Это определенно не ошибка transform.
Цикл заметок @Barry, который я добавил в пример, который пропускает только std::views::transform. Это работает нормально.
У @MarekR OP была такая же петля? Вы не добавляете никакой информации?





Проблема в std::regex_iterator и в том, что он тайник.
Этот тип в основном выглядит так:
class regex_iterator {
vector<match> matches;
public:
auto operator*() const -> vector<match> const& { return matches; }
};
Это означает, например, что, хотя тип ссылки этого итератора — T const&, если у вас есть две копии одного и того же итератора, они фактически дадут вам ссылки на разные объекты.
Теперь join_view<R>::iterator в основном выглядит так:
class iterator {
// the iterator into the range we're joining
iterator_t<R> outer;
// an iterator into *outer that we're iterating over
iterator_t<range_reference_t<R>> inner;
};
Что для regex_iterator примерно выглядит так:
class iterator {
// the regex matches
vector<match> outer;
// the current match
match* inner;
};
Что произойдет, если вы скопируете этот итератор? Копия inner по-прежнему относится к оригиналу outer! На самом деле они не являются независимыми, как вы ожидаете. Это означает, что если оригинал выходит за рамки, у нас есть оборванный итератор!
Вот что вы видите здесь: transform_view заканчивается копированием итератора (что, безусловно, разрешено делать), и теперь у вас есть оборванный итератор (вместо этого перемещается реализация libc++, поэтому он работает в таком случае как 康桓瑋указал) . Но мы можем воспроизвести ту же проблему без transform, если мы скопируем итератор и уничтожим оригинал. Например:
#include <ranges>
#include <regex>
#include <iostream>
#include <optional>
int main() {
std::string_view text = "The IP addresses are: 192.168.0.25 and 127.0.0.1";
std::regex regex{R"((\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3}))"};
auto a = std::ranges::subrange(
std::cregex_iterator(std::ranges::begin(text), std::ranges::end(text), regex),
std::cregex_iterator{}
);
auto b = a | std::views::join;
std::optional i = b.begin();
std::cout << std::string_view((*i)->first, (*i)->second) << '\n'; // fine
auto j = *i;
i.reset();
std::cout << std::string_view(j->first, j->second) << '\n'; // boom
}
Я не уверен, как будет выглядеть решение этой проблемы, но причина в std::regex_iterator, а не в views::join или views::transform.