🖋️

C++ で文字列を Unicode の意味でイテレートする

2024/05/28に公開

2024年5月現在、C++ には Unicode を扱う機能がないので、ICU というライブラリーを使う。ICU には C と C++ 向けの ICU4C と Java 向けの ICU4J がある。今回は C++ で使いたいので ICU4C を使う。

使うまで

私は macOS で fish を使っている。インストール方法は公式に載っている方法の他、Homebrew でも取ってこれる。

brew install icu4c

brew info icu4c で次のように表示されるが、

一部抜粋(fish 向けの設定方法が書いてある)
For compilers to find icu4c you may need to set:
  set -gx LDFLAGS "-L/usr/local/opt/icu4c/lib"
  set -gx CPPFLAGS "-I/usr/local/opt/icu4c/include"

/usr/local/opt/icu4c/lib 以下にライブラリー、/usr/local/opt/icu4c/include 以下にヘッダーが配置されるので、コンパイルするときはコンパイラーに伝える必要がある。基本機能は libicuuc.ホニャララ というライブラリにあるので伝える。なのでコンパイルは次のような感じである。

g++-14 -std=c++23 -I /usr/local/opt/icu4c/include -L /usr/local/opt/icu4c/lib -l icuuc main.cpp -o main

使い方

コードポイントでイテレーション

Zenn 上だと「ポプテピピック」が正規化されてしまうが、複数コードポイントで表現する。

#include <string>
#include <format>
#include <print>
#include <unicode/utypes.h>
#include <unicode/unistr.h>
#include <unicode/schriter.h>

UChar32 constexpr power_16_4 = 65536;
UChar32 constexpr power_16_5 = 1048576;

auto const unicode(UChar32 n) {
    return n < power_16_4 ? std::format("U+{:04X}", n)
        : n < power_16_5 ? std::format("U+{:05X}", n)
        : std::format("U+{:06X}", n);
}

int main() {
    auto const s = u8"aiueoあいうえお𠮷ポプテピピック";
    auto const us = icu::UnicodeString::fromUTF8(s);
    auto i = icu::StringCharacterIterator(us);
    for (i.setToStart(); i.hasNext();) {
        auto const n = i.next32PostInc();
        auto const uc = icu::UnicodeString(n);
        std::string c;
        uc.toUTF8String(c);
        std::println("{} {}", c, unicode(n));
    }
}
実行結果
a U+0061
i U+0069
u U+0075
e U+0065
o U+006F
あ U+3042
い U+3044
う U+3046
え U+3048
お U+304A
𠮷 U+20BB7
ホ U+30DB
 ゚ U+309A
フ U+30D5
 ゚ U+309A
テ U+30C6
ヒ U+30D2
 ゚ U+309A
ヒ U+30D2
 ゚ U+309A
ッ U+30C3
ク U+30AF

書記素でイテレーション

#include <string>
#include <print>
#include <unicode/unistr.h>
#include <unicode/utypes.h>
#include <unicode/brkiter.h>

int main() {
    auto const s = u8"aiueoあいうえお𠮷ポプテピピック";
    auto const us = icu::UnicodeString::fromUTF8(s);
    auto status = U_ZERO_ERROR;
    auto * it = icu::BreakIterator::createCharacterInstance(icu::Locale::getDefault(), status);
    it->setText(us);
    for (
        auto current = it->first(), next = it->next();
        next != icu::BreakIterator::DONE;
        current = next, next = it->next()
    ) {
        auto const ss = us.tempSubStringBetween(current, next);
        std::string o;
        ss.toUTF8String(o);
        std::println("{}", o);
    }
}
実行結果
a
i
u
e
o
あ
い
う
え
お
𠮷
ポ
プ
テ
ピ
ピ
ッ
ク

ロケールを設定する機能があったりいろいろできそうだがよく分かっていない。Unicode の機能をいろいろ使う場合は icu::UnicodeString にしていろいろ操作できる。C++ 標準の std::string にするには toUTF8String する。

参考にした公式以外のもの

公式ドキュメントが一番詳しいが、例が少ないのでツラい。

  1. masakielastic, ICU の BreakIterator を使って書記素を1つずつ取り出す, https://qiita.com/masakielastic/items/8386c76d5d897d980a73, 2015-02-02.
  2. zinbe, ICUでUTF-8の文字列を1文字ごとに分割する, https://qiita.com/zinbe/items/f8501b8da709dfad8b11, 2018-03-22.
  3. kale_core, C++ ICUメモ, 文字単位のループ処理, https://zenn.dev/link/comments/7582ab0057e249, 2024.

Discussion