C++でC風ライブラリを作る(商と剰余編)
高木です。おはようございます。
前回は絶対値編でした。
元ネタはstdlib.h
ヘッダのabs
関数ですので、今回は同じヘッダのdiv
関数を元ネタにしたいと思います。
div
関数にも型によってバリエーションがあり、ldiv
、lldiv
、imaxdiv
といったものが挙げられます。
どういうわけか符合無し整数型に対応した関数がありません。
符合付き整数と符合無し整数が混在した場合はちょっと面倒なので、その辺りは念入りに検討する必要がありそうです。
abs
関数と同様の理由でオーバーフローが発生することもあります。
具体的には、INT_MIN
を-1で割った場合に発生します。
また、ゼロ除算の問題も当然あります。
div_t
という構造体にも汎用性がありません。
テンプレートで利用しようとしたとき、引数の型によって返却値がdiv_t
やldiv_t
などを使い分ける必要があるからです。
型推論を使えばマシにはなりますが、ハッキリいってイケていません。
ボリュームが増えそうな引数の型が混在したケースは次回まわしにして、今回は2つの引数の型が同じ場合にしぼって考えることにします。
同じ型の2つの引数取る関数を多重定義した場合、(汎整数昇格したあとの)型が異なる実引数を渡そうとすると多重定義が解決できずにエラーになります。
うっとうしく感じることもありますが、うっかりミスをやってしまうよりはエラーにしてしまったほうが間違いがありません。
エラー処理に関しては例外を使うことにします。
ゼロ除算とオーバーフローについてです。
ゼロ除算の場合はstd::invalid_argument
を、オーバーフローの場合はstd::overflow_error
を送出することにしましょう。
返却値の型はcloverfield::div_t
クラステンプレートを定義して、それを使うことにします。
まずはこのクラステンプレートの定義から進めることにしましょう。
1 2 3 4 5 6 7 8 9 | namespace cloverfield { template <typename T> struct div_t { T quot; T rem; }; } |
標準Cライブラリでは、このquot
とrem
の順序が処理系定義でした。
そういうところが使い勝手の悪さにつながっています。
今回は順序は固定ですので、それだけでもずいぶん扱いやすくなります。
次に実際の関数の定義を行います。
今回は代表してint
とunsigned int
の2つの型だけを掲載しますが、実際には他の型についても同様に定義することになります。
まずは、比較的簡単なunsigned int
版から見ていきましょう。
1 2 3 4 5 6 7 8 9 10 | namespace cloverfield { constexpr div_t<unsigned int> div(unsigned int numer, unsigned int denom) { return { (denom == 0 ? throw std::invalid_argument("cloverfield::div"), 0 : numer / denom), numer % denom }; } } |
unsigned int
版ではオーバーフローが発生することはありませんので、ゼロ除算だけ判定しています。
次はint
版です。
1 2 3 4 5 6 7 8 9 10 | namespace cloverfield { constexpr div_t<int> div(int numer, int denom) { return { (denom == 0 ? throw std::invalid_argument("cloverfield::div"), 0 : (numer == INT_MIN && denom == -1 ? throw std::overflow_error("cloverfield::div"), 0 : numer / denom)), numer % denom }; } } |
ゼロ除算とオーバーフローを検出しないとイケませんので若干複雑になりました。
浮動小数点型については作成を見送りました。
constexpr
にするのが面倒なので、対称性に欠けるからです。
頑張ればconstexpr
にすることも可能ですが、trunc
やfmod
といった関数のconstexpr
版を先に作成する必要がありそうです。
それらの関数を作成する機会があれば、その時点で着手してもよいでしょうね。