はじめに
std::rel_opsやboost::operatorsを使うことで、演算子の実装の手間を省くことができます。実装の手間を省くことができるというのはDRY原則からしても、とても素晴らしいことです。
通常、これらを利用する場合operator <とoperator==を実装してくれれば、残ってる奴らは全部やっとくよ!というものですが、実はoperator <だけで、すべて済みますという話です。
mix-inの実装
boost::operatorsと同じですが、まず比較演算子の実装です。
template <typename T>
class Operators {
public:
friend bool operator >(const T& lhs, const T& rhs) { return rhs < lhs; }
friend bool operator<=(const T& lhs, const T& rhs) { return !(lhs > rhs); }
friend bool operator>=(const T& lhs, const T& rhs) { return !(lhs < rhs); }
};
このOperatorsを継承することで、operator <を実装しとけばoperator >もoperator<=もoperator>=も使えるようになるということです。
等値演算子も追加しちゃう
実はa < bかb < aならa != bなので、これを利用することで等値演算子も追加できます。
template <typename T>
class Operators {
public:
friend bool operator >(const T& lhs, const T& rhs) { return rhs < lhs; }
friend bool operator<=(const T& lhs, const T& rhs) { return !(lhs > rhs); }
friend bool operator>=(const T& lhs, const T& rhs) { return !(lhs < rhs); }
friend bool operator!=(const T& lhs, const T& rhs) { return (lhs < rhs) || (rhs < lhs); }
friend bool operator==(const T& lhs, const T& rhs) { return !(lhs != rhs); }
};
こいつ継承することで、operator <を実装しとけば、加えてoperator==もoperator!=も使えるようになります。
なんでboostとかに用意されてないわけ?
問題が仮にあったとして、選択肢としてこんな感じのmix-inクラスを持っておくのは、いいことだと思いますと前置きした上で、ちょっとした雑談を。
パフォーマンスから考える
operator==もoperator!=も、それなりにパフォーマンスが必要なケースが多く、関数を引きまわすとパフォーマンス上よくはありません。(実際にバイナリになったときに影響が出るかどうかは置いといて)
ですので、operator==はシンプルにフィールド比較するくらいが精神衛生上も良いかもしれません。
ユースケースから考える
演算子添加のユースケースとしては以下が考えられます。
- 等値演算子だけ実装して、比較演算子は実装しない
- 比較演算子だけ実装して、等値演算子は実装しない
- 比較演算子も実装して、等値演算子も実装する
個人的には、テストも書きたいので1は必要というケースがもっとも多い気がします。
そのあとstd::setの要素や、std::mapのキーにしたいときには、operator <を実装して、せっかくなのでboost::less_than_comparableをついでに継承することがありますね。これが3のケースですね。
2から3という状況は、うーん…たしかにあまりないかもしれませんね。結局、これがないのは、そこまで需要がないということなんでしょう。
まとめ
便利です。