11
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

自動的な比較演算子の生成

Last updated at Posted at 2015-04-05

比較演算子の定義というものは実に面倒なものです。
せめて逆の意味の演算子は自動的に定義してくれてもいいんじゃないですか。
そこで、せめて逆の意味の演算子を自動的に定義してくれるものを作っておくと便利です。
手っ取り早く結論を見せろと言う方は、#方法3を御覧ください。

#方法1 比較演算子を持つクラスをMixinする

curiously recurring template pattern を使って、自分の派生クラスを手に入れます。
そしたら、全ての比較演算子を派生クラスの実装に依存したかたちで定義してあげます。

template < class derived >
struct comparable
{ // provide comparison operators
  bool operator == ( const derived& right ) const
  {
    return !( static_cast < const derived& >( *this ) != right ); 
  }
  bool operator != ( const derived& right ) const 
  { 
    return !( static_cast < const derived& >( *this ) == right );
  }
  bool operator < ( const derived& right ) const 
  {
    return !( static_cast < const derived& >( *this ) >= right );
  }
  bool operator <= ( const derived& right ) const 
  { 
    return !( static_cast < const derived& >( *this ) > right ); 
  }
  bool operator > ( const derived& right ) const
  {
    return !( static_cast < const derived& >( *this ) <= right );
  }
  bool operator >= ( const derived& right ) const
  {
    return !( static_cast < const derived& >( *this ) < right );
  }
};

実際のコードはこちら。

class something
  : public comparable // provide comparison operators
{
  int n;
public:
  something( int n = 0 ) : n( n ) {}
  bool operator == ( const something& right ) const { return this->n == right.n; }
  bool operator >= ( const something& right ) const { return this->n >= right.n; }
  bool operator > ( const something& right ) const { return this->n > right.n; }
};

int main()
{
  something s1( 10 );
  something s2( 10 );
  std::cout << std::boolalpha 
    << ( s1 != s2 ) << std::endl 
    << ( s1 < s2 ) << std::endl
    << ( s1 >= s2 ) << std::endl;
}

最低3つ定義するだけで、他の全ての演算子を利用することができます。
しかしこれには重大な欠点があります。それは、public継承をすることで、意図しないポリモーフィズムに使われる可能性があるということです。

comparable < something >* p = new something(); // legal
delete p; // ~something() is never called

しかしcomparableのデストラクタをvirtualにする選択肢はもちろんありませんから、private継承をして比較演算子を再宣言してあげることになります。

class something
  : private comparable // provide comparison operators
{
  using _Comparable = comparable < something >;
  int n;
public:
  something( int n = 0 ) : n( n ) {}
  bool operator == ( const something& right ) const { return this->n == right.n; }
  bool operator >= ( const something& right ) const { return this->n >= right.n; }
  bool operator > ( const something& right ) const { return this->n > right.n; }
  using _Comparable::operator!=;
  using _Comparable::operator<;
  using _Comparable::operator<=;
};

少し微妙ですがどうにかならないものですかね。

#方法2 テンプレートなグローバル比較演算子を定義する

もうそのまんまです。ただし、グローバル関数として比較演算子を作りますが、グローバル空間に置いてはいけません。たくさんの意図しないクラスが影響を受ける可能性があるからです。
また、ADLのはたらく空間に対象のクラスをつくる必要があります。

namespace comparable_space
{
template < class Ty1, class Ty2 >
bool operator == ( const Ty1& right, const Ty2& left ) { return !( right != left ); }
template < class Ty1, class Ty2 >
bool operator != ( const Ty1& right, const Ty2& left ) { return !( right == left ); }
template < class Ty1, class Ty2 >
bool operator < ( const Ty1& right, const Ty2& left ) { return !( right >= left ); }
template < class Ty1, class Ty2 >
bool operator <= ( const Ty1& right, const Ty2& left ) { return !( right > left ); }
template < class Ty1, class Ty2 >
bool operator > ( const Ty1& right, const Ty2& left ) { return !( right <= left ); }
template < class Ty1, class Ty2 >
bool operator >= ( const Ty1& right, const Ty2& left ) { return !( right < left ); }

class something
{
  int n;
public:
  something( int n = 0 ) : n( n ) {}
  bool operator == ( const something& right ) const { return this->n == right.n; }
  bool operator >= ( const something& right ) const { return this->n >= right.n; }
  bool operator > ( const something& right ) const { return this->n > right.n; }
};
} // namespace comparable_space

実際に使ってみましょう。

int main( int arg, char** argv )
{
  comparable_space::something s1;
  comparable_space::something s2;
  std::cout << ( s1 != s2 );
}

これはいい感じに動くのではないかと思います。
しかし、ここでも問題があります。
それは、ADLの縛りを受けてしまい、名前空間が自由にできないということです。
また、テンプレートなグローバル関数なので、ひと目で比較演算子が使えることが分かりません。
メンバ関数として定義されていないoperator !=をどうして使えると思うでしょうか。

#方法3 テンプレートなグローバル比較演算子を定義し、それを利用可能にするタグを用意する。

最終的な解法として、方法1と方法2のいいとこ取りをします。

namespace comparable_space
{
struct comparable {}; // tag

template < class Ty1, class Ty2 >
bool operator == ( const Ty1& right, const Ty2& left ) { return !( right != left ); }
template < class Ty1, class Ty2 >
bool operator != ( const Ty1& right, const Ty2& left ) { return !( right == left ); }
template < class Ty1, class Ty2 >
bool operator < ( const Ty1& right, const Ty2& left ) { return !( right >= left ); }
template < class Ty1, class Ty2 >
bool operator <= ( const Ty1& right, const Ty2& left ) { return !( right > left ); }
template < class Ty1, class Ty2 >
bool operator > ( const Ty1& right, const Ty2& left ) { return !( right <= left ); }
template < class Ty1, class Ty2 >
bool operator >= ( const Ty1& right, const Ty2& left ) { return !( right < left ); }
} // namespace comparable_space

namespace project
{
class something
  : private comparable_space::comparable
{
  int n;
public:
  something( int n = 0 ) : n( n ) {}
  bool operator == ( const something& right ) const { return this->n == right.n; }
  bool operator >= ( const something& right ) const { return this->n >= right.n; }
  bool operator > ( const something& right ) const { return this->n > right.n; }
};
} // namespace project

ADLの基底クラスの名前空間も探索するという性質を利用します。
これにより、private継承で、長ったらしいusing宣言もなく、さらにcomparableなクラスであることを明示することができました。
利用するときは、以下のように使えます。

int main( int arg, char** argv )
{
  project::something s1;
  project::something s2;
  std::cout << ( s1 != s2 );
}

寝て起きたら気がついたもので突貫ですが、なかなか使えそうな予感がします。

11
10
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?