« google ChromeでSHA-1のベンチマーク | メイン

VCでUTF32文字列に対してboost::regexを使う

Visual StudioのC++開発環境ではsizeof(wchar_t) == 2なのでstd::wstringではサロゲートペアを考慮する必要があります.
それを避けるためにUTF32文字列で文字列処理を行いたい場合があると思います.その場合,

typedef unsigned int uint32_t;
typedef std::basic_string<uint32_t> u32stirng;

などの定義をして文字列を使うことになるでしょう.これらの型はC++0xでは標準になる予定です.
実際には利便性のためにchar*との自然な変換メソッドなどを追加すると思いますが,ここでは省略します.

boost::regexでもstd::stringと同様にテンプレートパラメータに文字の型を与えることができ,

typedef boost::basic_regex<uint32_t> u32regex;
typedef boost::match_results<u32string::const_iterator> u32match;

などと定義するとu32stringに対しても正規表現が使えるようになります.

が,\w, \s, \dや[:digit:]などを使うとややこしい問題が発生します.
これらを使うと現在の実装(boost 1.38)では内部的にuint32_tからcharにキャストしてから判別するようで,全然関係のない文字がマッチしてしまうことがあります(これはboostが悪いというわけではありません).

たとえば

/* これはUTF-8 */
const uint32_t str_[] = { 0x3053, 0x308c, 0x306f, 'U', 'T', 'F', '-', '8', 0 };
にたいして"\w+"をマッチさせると,先頭の一文字がマッチしてしまいます.これは0x3053はcharにキャストされ,0x53になり'S'と看做されてしまうからです.

ここではこれを回避する最低限の修正について説明します.

boost::regexでは上記の文字の型の判定にstd::ctypeを利用します.
したがって,その特殊化を用意すればひとまず問題を回避できます.
std::ctypeが持つべきメソッドはctype - C++ Referenceなどを参考にして用意します.
ざっと調べたところではis, tolower, widenの三つを用意すればよいようでした(本当は全て用意すべきですが).

それぞれの特殊化を用意しましょう.

namespace std {
template<>
bool ctype<uint32_t>::is(std::ctype_base::mask m, uint32_t c) const
{
    if (c & ~0xFFU) return false;
    return use_facet<ctype<char> >(std::locale()).is(m, static_cast<char>(c));
}
template<>
uint32_t ctype<uint32_t>::tolower(uint32_t c) const
{
    if ('A' <= c && c <= 'Z') return c - 'A' + 'a';
    return c;
}
template<>
uint32_t ctype<uint32_t>::widen(char c) const
{
    return c;
}
}
勝手にstd空間に関数を定義しているのは一見奇妙に思えますが,std空間にテンプレートの特殊化を追加することは規格上認められているので問題ありません.

本来はstd::ctype_base::maskにはspace, upperなど文字の特性を判別するフラグが入ってくるのでUnicodeの規格に応じて正しく分岐したほうがよいかもしれません.
しかし純粋にasciiだけをチェックしたい場合もあるでしょうから,ここでは1byte文字でなければfalseにして,1byteのときは既存のcharに対するctypeを呼ぶことにしました.
スペースをasciiの0x20だけでなく全角スペース(0x3000)や,他のUnicodeのスペースを判別したい場合はisを適宜修正してください.

この特殊化を追加することで,上記のマッチングは正しく"UTF"を返すようになります.
サンプルコード

(注意)Linux上のgccではこれらの修正だけでは正しく動作しませんが,今回は触れません.

なお,isの中のuse_facetをする部分

return use_facet<ctype<char> >(std::locale()).is(m, static_cast<char>(c));

は極めて重たい処理ですので,static変数にキャッシュしておきそれを使うのがよいです.

static const std::ctype<char>& cache = use_facet<ctype<char> >(std::locale());
return cache.is(m, static_cast<char>(c));

あるいは,std::ctypeがもつtable()メソッドを引き出して,

static struct Custom : public std::ctype<char> {
    const mask * table() const { return std::ctype<char>::table(); }
} custom;
static const mask* maskTbl = custom.table();
return (maskTbl[(unsigned char)c] & m) != 0;

などとするのがよいでしょう.

コメントを投稿

(いままで、ここでコメントしたことがないときは、コメントを表示する前にこのブログのオーナーの承認が必要になることがあります。承認されるまではコメントは表示されません。そのときはしばらく待ってください。)