boost::xpressiveのrangeでハマる

boost::xpressiveを使って「コントロール文字を除いたUS-ASCII文字(0x20〜0x7e)」以外か判定しようとしたら、かなり恥ずかしい間違いをしたのでメモ。


元々次のような正規表現で判定をしようと思っていた。

#include <iostream>
#include <boost/xpressive/xpressive.hpp>

int main()
{
	using namespace boost::xpressive;

	// [^\x20-\x1e] でいいじゃないかというツッコミはなしで
	sregex re = range('\x00', '\x1f') | range('\x7f', '\xff');

	std::cout << "0x1f: 1 == " << regex_match(std::string("\x1f"), re) << std::endl;
	std::cout << "0x20: 0 == " << regex_match(std::string("\x20"), re) << std::endl;
	std::cout << "0x7e: 0 == " << regex_match(std::string("\x7e"), re) << std::endl;
	std::cout << "0x7f: 1 == " << regex_match(std::string("\x7f"), re) << std::endl;
	std::cout << "0x80: 1 == " << regex_match(std::string("\x80"), re) << std::endl;

	return 0;
}

すると、結果は次のようになった。

0x1f: 1 == 1
0x20: 0 == 0
0x7e: 0 == 0
0x7f: 1 == 0
0x80: 1 == 0

最初はマルチバイト周りのバグかとも思ったが、よくよく考えるとcharは符号付きなので、'\x7f'==127、 '\x80'==-128、 '\xff'==-1 になるんだった。
つまり、range('\x7f', '\xff') は (127<=c && c<=-1) の判定をしていることになる。
確かに 0x7f(127) も 0x80(-128) もこの条件を満たせない。


というわけで正しくは以下のようにしなければならなかった。

sregex re = range('\x00', '\x1f') | '\x7f' | range('\x80', '\xff');


いやはや、今更符号周りでハマるとは思わなかった。