C++でC風ライブラリを作る(文字列→整数変換編3)
高木です。おはようございます。
前回予告したように、これまで作成してきたstrto
関数テンプレートを、利便性を向上させるために多重定義を行うとともに、エラー処理を改善したいと思います。
今回はソースコードのボリュームがなかり増えることが予想されますが、さすがにそろそろ終わりにします。
順番は前後しますが、まずはエラー処理からです。
オリジナルのstrtoul
関数などでは、オーバーフローが発生するとerrno
にERANGE
を設定した上で、返却値型の最大値(ULONG_MAX
など)を返します。
同じようにしてもいいのですが、errno
に依存するのはマルチスレッド環境で微妙なことになります。
C++なら一番素直なエラーの通知方法は例外を使うことでしょう。
ただ、例外を使うと実行時間がかかりかかりますので、ある程度の頻度でオーバーフローが発生することを前提とする場合にはパフォーマンスに問題が出ます。
あるいは、エラー処理用のコールバックを渡す方法も考えられます。
これなら細かい制御が可能ですが、オーバーフローしたかどうかを知りたいだけなのでやり過ぎな感があります。
今回はオーバーフローが発生した場合にtrue
に設定するbool
型の変数へのポインタを渡すという安直な方法を採用することにします。
ただし、空ポインタを渡した場合は例外を送出することにしましょう。
省略時実引数として空ポインタを渡すことにします。
前々回作成したstrto_helper
関数テンプレートでは、オーバーフローが発生した場合はそこで文字列の解析をストップしていました。
オリジナルのstrtoul
関数などでは、数字が続く限り、途中でオーバーフローが生じてもストップしませんので、その仕様に合わせることにします。
それらを盛り込んだcode>strto_helper関数テンプレートは次のようになります。
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 | namespace cloverfield { namespace detail { template <typename Type, int Radix, typename Iterator> Type strto_helper(Iterator first, Iterator last, Iterator* endit, bool* overflow = nullptr) { static_assert(std::is_unsigned<Type>::value, "Type must be unsigned integral type."); using char_type = typename std::iterator_traits<Iterator>::value_type; Type result = 0; bool error = false; constexpr auto max = std::numeric_limits<Type>::max(); for (; first != last; ++first) { auto c = tolower(*first); int d; if (char_type('0') <= c && c <= char_type('9')) d = c -'0'; else if (char_type('a') <= c && c <= char_type('z')) d = c - 'a' + 10; else break; if (d >= Radix) break; if (result > (max - d) / Radix) error = true; if (!error) result = result * Radix + d; } if (error) result = max; if (endit) *endit = first; if (overflow) *overflow = error; else if (error) throw std::overflow_error("cloverfield::strto"); return result; } } } |
末尾の引数overflow
の追加に伴い、前回作成したstrto
関数テンプレートも次のように変更になります。
static_assert
による基数判定もこちらに移動しています。
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 | namespace cloverfield { template <typename Type, int Radix, typename Iterator> Type strto(Iterator first, Iterator last, Iterator* endit, bool* overflow = nullptr) { static_assert(Radix == 0 || 2 <= Radix && Radix <= 36, "Radix must be 0 or 2 to 36."); bool sign = detail::space_sign(first, last); Type u; if (Radix == 0) { switch (detail::radix_prefix(first, last)) { case 2: u = detail::strto_helper<Type, 2>(first, last, endit, overflow); break; case 8: u = detail::strto_helper<Type, 8>(first, last, endit, overflow); break; case 16: u = detail::strto_helper<Type, 16>(first, last, endit, overflow); break; default: u = detail::strto_helper<Type, 10>(first, last, endit, overflow); break; } } else { u = detail::strto_helper<Type, Radix>(first, last, endit, overflow); } return sign? -u : u; } } |
最後に、利便性向上のための多重定義を行います。
目標としては、basic_string
やvector
などのコンテナを渡せるようにすること、文字配列を渡せるようにすること、文字型へのポインタを渡せるようにすることです。
文字配列と文字型へのポインタを素直に多重定義しようとすると、曖昧になってコンパイルできなくなってしまいます。
今回は、文字型へのポインタではなく、文字型へのポインタへの参照を多重定義することで解決しました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | namespace cloverfield { template <typename Type, int Radix, typename Container> inline Type strto(Container& c, decltype(std::begin(c))* endit, bool* overflow = nullptr) { return strto<Type, Radix>(std::begin(c), std::end(c), endit, overflow); } template <typename Type, int Radix, typename Char, std::size_t N> inline Type strto(Char (&s)[N], Char** endit, bool* overflow = nullptr) { return strto<Type, Radix>(&s[0], &s[N], endit, overflow); } template <typename Type, int Radix, typename Char> inline Type strto(Char* const& s, Char** endit, bool* overflow = nullptr) { return strto<Type, Radix>(s, (Char*)nullptr, endit, overflow); } } |
当ブログでの投稿1回辺りの目標は1,000文字なのですが、今回は4,000文字近くになってしまいました。
このstrto
関数テンプレートは、さらに拡張して符合付き整数を扱えるようにすること、そして浮動小数点数まで含めた全算術型を扱えるようにしたいと考えています。
ただ、このネタもそろそろ飽きてきましたので、別の関数に先に手を付けたいと思います。