はじめに
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という状況は、うーん…たしかにあまりないかもしれませんね。結局、これがないのは、そこまで需要がないということなんでしょう。
まとめ
便利です。