UTF-8 の文字列を操作するための補助関数群

UTF-8 の文字列を操作するための補助関数群を作ってみました.当初,find() などの特定の文字列を見つける部分の実装が面倒そうだなぁと思っていたのですが,UTF-8 の 2 バイト目以降には 1 バイト目に出現しうる値は出現しないのですね.

ビットパターンは以下のようになっている。

0xxxxxxx                             (00-7f) 7bit
110yyyyx 10xxxxxx                    (c0-df)(80-bf) 11bit
1110yyyy 10yxxxxx 10xxxxxx           (e0-ef)(80-bf)(80-bf) 16bit
11110yyy 10yyxxxx 10xxxxxx 10xxxxxx  (f0-f7)(80-bf)(80-bf)(80-bf) 21bit
UTF-8 - Wikipedia

そんな訳で find() などは std::string の find() をそのまま使えば良さそうなので,その他にUTF-8 の文字列を操作する上で必要となりそうなのは,文字数が何文字であるか,n 文字を取得する,n 文字先に進める(または後ろに戻す),あたりになります.今回は,これらの関数を作成してみました.

namespace clx {
    namespace utf8 {
        template <class InIter>
        size_t get(InIter& first, InIter last);
        
        template <class InIter, class OutIter>
        OutIter get(InIter& first, InIter last, OutIter out);
        
        template <class InIter, class OutIter>
        OutIter get(InIter& first, InIter last, OutIter out, size_t n);
        
        template <class InIter>
        size_t peek(InIter first, InIter last);
        
        template <class InIter>
        void advance(InIter& pos, int n);
        
        template <class InIter>
        size_t distance(InIter first, InIter last);
        
        size_t distance(const std::string& s);
        size_t distance(const char* s);
    }
}

get() は,first の位置から 1 文字取得します.引数が入力イテレータ (InIter) のみの場合は,1 文字分の値を size_t の変数に格納し,その値を返します.引数に出力イテレータ (OutIter) も指定された場合は,指定された出力イテレータに 1 文字分のデータ (1 〜 4 バイト) を格納します.出力イテレータの他に整数も指定した場合には,指定した文字数分だけ格納します.

get() は,引数から想像できると思いますが,引数に指定された入力イテレータ first を取得したバイト数分だけ移動させます.これに対して,peek() は,引数に指定された入力イテレータの値を変化させません.ただし,入力イテレータとして istream_iterator などを指定された場合には,peek() でも取得したバイト数分だけ移動してしまいます.

advance() は,現在の位置 pos から n 文字分だけイテレータを先に進めます.ただし,n に負の数を指定した場合には n 文字分だけイテレータを後ろに戻します.

distance() は,first から last の間に存在する文字数を返します.

尚,現状では,どの関数も char 型である事を想定しています.

Example
#include <iostream>
#include <string>
#include "clx/utf8.h"

int main(int argc, char* argv[]) {
    std::string s = "はろーわーるど! This is a UTF-8 test program.";
    std::cout << "size: " << s.size() << std::endl;
    std::cout << "char count: " << clx::utf8::distance(s) << std::endl;
    
    // advance のテスト.
    std::string::iterator pos = s.begin();
    int n = 0;
    while (pos != s.end()) {
        clx::utf8::advance(pos, 1);
        clx::utf8::advance(pos, -1);
        clx::utf8::advance(pos, 1);
        ++n;
    }
    std::cout << "char count: " << std::dec << n << std::endl;
    
    // 1 文字ずつ取得して 16 進表記で出力.
    pos = s.begin();
    while (pos != s.end()) {
        std::string dest;
        clx::utf8::get(pos, s.end(), std::insert_iterator<std::string>(dest, dest.end()));
        for (size_t i = 0; i < dest.size(); ++i) {
            std::cout << "<" << std::hex << (static_cast<size_t>(dest.at(i)) & 0xff) << ">";
        }
        std::cout << std::endl;
    }
    
    return 0;
}
実行結果
[clx_example]$ ./a
size: 54
char count: 38
char count: 38
<81>
<82><8d>
<83>
<82><8f>
<83>
<82><8b>
<81>
<81>
<20>
<54>
<68>
・・・(以下略)・・・