‹regex› проблемы с кириллическими символами

Я пытаюсь использовать стандартную библиотеку <regex> для сопоставления некоторых кириллических слов:

  // This is a UTF-8 file.
  std::locale::global(std::locale("en_US.UTF-8"));

  string s {"Каждый охотник желает знать где сидит фазан."};
  regex re {"[А-Яа-яЁё]+"};

  for (sregex_iterator it {s.begin(), s.end(), re}, end {}; it != end; it++) {
    cout << it->str() << "#";
  }

Однако это не похоже на работу. Приведенный выше код приводит к следующему:

  Кажд�#й#о�#о�#ник#желае�#зна�#�#где#�#иди�#�#азан#

вместо ожидаемого:

  Каждый#охотник#желает#знать#где#сидит#фазан

Код символа «�» выше — \321.

Я проверил регулярное выражение, которое использовал с grep, и оно работает, как и ожидалось. Моя локаль en_US.UTF-8. И GCC, и Clang дают одинаковый результат.

Есть ли что-то, что я упускаю? Есть ли способ "приручить" <regex>, чтобы он работал с кириллическими символами?


person undercat    schedule 03.03.2020    source источник
comment
я не уверен в этом, но разве вы не должны использовать std::wstring или std::u32string , std::wregex или boost::u32regex и так далее?   -  person John Ding    schedule 03.03.2020
comment
Ваши строки закодированы в utf-8?   -  person Alan Birtles    schedule 03.03.2020
comment
@JohnDing Вы были абсолютно правы. Использование wstring и др. помогло. Если вы не возражаете, я вскоре отвечу на свой вопрос, используя эти знания.   -  person undercat    schedule 03.03.2020
comment
@undercatapplaudsMonica Конечно нет, давай.   -  person John Ding    schedule 03.03.2020


Ответы (2)


Чтобы такие диапазоны, как А-Я, работали правильно, необходимо использовать std::regex::collate.

Константы
...
сопоставление Диапазоны символов в форме "[a-b]" зависят от региональных настроек.

Изменение регулярного выражения на

std::regex re{"[А-Яа-яЁё]+", std::regex::collate};

дает ожидаемый результат.


В зависимости от кодировки исходного файла вам может потребоваться префикс строки регулярного выражения с префиксом u8

std::regex re{u8"[А-Яа-яЁё]+", std::regex::collate};
person Olaf Dietsche    schedule 03.03.2020
comment
Я могу подтвердить, что это работает и кажется менее навязчивым решением по сравнению с использованием wchars, удивлен, как мало обращений regex::collate в Google! - person undercat; 03.03.2020

Кириллические буквы представлены в виде многобайтовых последовательностей в кодировке UTF-8. Поэтому одним из способов решения проблемы является использование «широкой» версии string под названием wstring. Другие функции и типы, работающие с широкими символами, также должны быть заменены их «многобайтовой» версией, обычно это делается путем добавления w к их имени. Это работает:

std::locale::global(std::locale("en_US.UTF-8"));

wstring s {L"Каждый охотник желает знать где сидит фазан."};
wregex re {L"[А-Яа-яЁё]+"};

for (wsregex_iterator it {s.begin(), s.end(), re}, end {}; it != end; it++) {
  wcout << it->str() << "#";
}

Выход:

Каждый#охотник#желает#знать#где#сидит#фазан#

(Спасибо @JohnDing за предложение этого решения.)


Альтернативное решение — использовать regex::collate, чтобы сделать регулярные выражения чувствительными к локали с помощью обычных строк, см. этот ответ от @OlafDietsche для деталей. Эта тема прольет свет на то, какое решение может быть более предпочтительным в ваших обстоятельствах. (Оказывается, в моем случае collate было лучше!)

person undercat    schedule 03.03.2020
comment
В этом сообщении указано, что он может работать с utf-8: stackoverflow.com/questions/11254232/ - person Alan Birtles; 03.03.2020