C++でC風ライブラリを作る(文字種別編)
高木です。おはようございます。
前回は準備編でしたので、今回からは本格的にライブラリを作っていくことにします。
あくまでもシンプルに気負うことなく作っていきたいので、小難しいことは一切パスです。
作っていくうちにいろいろ欲が出て、凝った仕様や実装にしたくなるところをグッとこらえて、シンプルなものにできたらと思います。
今回作るのは、Cの標準ライブラリでは<ctype.h>で宣言されるisalpha
などの文字種別関連の関数をC++で再設計することにします。
この類いの関数はシンプルなようで、フルスペックにしようと思うと複雑になります。
ロケールの影響を受けるからです。
しかし、実際に使う上ではCロケールだけでかまいませんし、それ以上のことをやられるとかえって邪魔なことのほうが多いんですよ。
たとえば、isdigit
は単純に'0'~'9'
の場合にtrue
を返して欲しいのですが、ローマ数数字だとか丸付き数字だとかでもtrue
になってしまうと使い勝手が悪いだけです。
確かにそういう判定をしたいこともあるとは思いますが、その場合は特殊用途の関数を別途用意するほうが嬉しいのです。
また、Cのis~
系関数は引数にint
型を取ります。
その上で、値が0~UCHAR_MAX
またはEOF
以外であれば未定義の動作になります。
文字列中の各文字をひとつずつ判定したい場合には、都度unsigned char
型にキャストしなければならず非常に面倒です。
さらに、char
型版とwchar_t
版では関数名が異なる上に、char16_t
型版やchar32_t
版がありません。
Cの場合は多重定義ができないので型ごとに別の関数名にするのはしかたがありませんが、C++だとこれはイケてませんね。
というか、次のような処理系の仮定を追加すれば、多重定義する必要すらなさそうです。
- 各文字型の値のうち、
0~0x7f
はASCII相当とする。
「ASCII相当」という微妙な表現をしてしまいましたが、ASCIIかASCIIと概ね互換性のある文字コードを仮定するということです。
ここでも小難しい定義を行う気はありません。
このようにしておけば、引数の型をstd::uint_least32_t
型にしておくことで多重定義せずに済みそうです。
実際に作ってみたのが下記のコードです。
長いのと、やろうとしていることは自明なのでコメントは省略しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 | #ifndef CLOVERFIELD_CTYPE_HH_ #define CLOVERFIELD_CTYPE_HH_ #include <cstdint> namespace cloverfield { constexpr bool isdigit(std::uint_least32_t c) { return (c - '0') < 10; } constexpr bool islower(std::uint_least32_t c) { return (c - 'a') < 26; } constexpr bool isupper(std::uint_least32_t c) { return (c - 'A') < 26; } constexpr bool isalpha(std::uint_least32_t c) { return islower(c | 0x20); } constexpr bool isalnum(std::uint_least32_t c) { return isalpha(c) || isdigit(c); } constexpr bool iscntrl(std::uint_least32_t c) { return c < 0x20 || c == 0x7f; } constexpr bool isblank(std::uint_least32_t c) { return c == ' ' || c == '\t'; } constexpr bool isspace(std::uint_least32_t c) { return (c - 0x9) < 5 || c == ' '; } constexpr bool isgraph(std::uint_least32_t c) { return (c - 0x21) < 0x5e; } constexpr bool isprint(std::uint_least32_t c) { return (c - 0x20) < 0x5f; } constexpr bool ispunct(std::uint_least32_t c) { return isgraph(c) && !isalnum(c); } constexpr bool isxdigit(std::uint_least32_t c) { return isdigit(c) || ((c | 0x20) - 'a') < 6; } constexpr bool isoctal(std::uint_least32_t c) { return (c - '0') < 8; } constexpr bool isascii(std::uint_least32_t c) { return c < 0x80; } template <typename charT> constexpr charT tolower(charT c) { return isupper(static_cast<std::uint_least32_t>(c)) ? c - 'A' + 'a' : c; } template <typename charT> constexpr charT toupper(charT c) { return islower(static_cast<std::uint_least32_t>(c)) ? c - 'a' + 'A' : c; } } #endif // CLOVERFIELD_CTYPE_HH_ |
<ctype.h>の関数は表引きで実装することが多いのですが、今回は32ビットの文字型を扱うこともあり、表引きにはせず計算で求めるようにしました。
一応、constexpr
にしていますので、多少は利便性が増したかと思います。
to~
系関数はテンプレートで実装してみました。