Канонические операторы отношений (==, <, ...) - PullRequest
1 голос
/ 16 октября 2011

Рассмотрим структуру (как в случае глупого объединения нескольких членов) с членами, которые реализуют определенное отношение R (например, <):

struct X {
  A a;
  B b;
};

Для большинства операторов существует каноническое определение для X R X. Например:

bool operator<(X const& x1, X const& x2) {
  if ((x1.a < x2.a) || (x2.a < x1.a)) // I intentionally did not use != here
    return x1.a < x2.a;
  if ((x1.b < x2.b) || (x2.b < x1.b))
    return x1.b < x2.b;
  return false;
}

Это довольно скучно для всех операторов, особенно если у вас довольно много членов, а не только одна такая структура.

Как видите, operator< over X зависит только от operator< его типов членов (A, B), помимо использования bool || bool.

Есть ли способ указать такие операторы в общем (через шаблоны или встроенные функции?). Повысить это не вариант (но было бы интересно, если бы он все-таки смог это сделать).

Было бы еще лучше, если бы вы могли указать порядок оценки членов (для скорости).

Редактировать Этот вопрос касается C ++ 03, так как в противном случае вы могли бы использовать std::tuple, я думаю.

Ответы [ 2 ]

0 голосов
/ 16 октября 2011

Поскольку, по-видимому, решения не для буста, я создал несколько шаблонов магии, которые я публикую в качестве ответа на случай, если у кого-то возникнет такая же проблема;

Версия 1: явные аргументы

namespace multioperator {

  enum LazyBool {
    LB_false = false,
    LB_true = true,
    LB_undefined
  };

  template <typename Cmp, typename B> class Operator {
    public:
      typedef typename Cmp::first_argument_type A;
    private:
      A const& a1;
      A const& a2;
      B const& b;
    public:
      Operator(A const& a1, A const& a2, B const& b)
        : a1(a1), a2(a2), b(b) {
      }
      operator bool() const {
        switch (static_cast<LazyBool>(Cmp(a1,a2))) {
          case LB_false:
            return false;
          case LB_true:
            return true;
          case LB_undefined:
          default: // g++ does not understand that we have all branches :(
            return static_cast<bool>(b);
        }
      }
  };

  template <typename Fn> class BinaryFunctorMonad {
    public:
      typedef typename Fn::first_argument_type first_argument_type;
      typedef typename Fn::second_argument_type second_argument_type;
      typedef typename Fn::result_type result_type;
    private:
      first_argument_type const& a;
      second_argument_type const& b;
    public:
      BinaryFunctorMonad(first_argument_type const& a, second_argument_type const& b)
        : a(a), b(b) {
      }
      operator result_type() {
        return Fn()(a,b);
      }
  };

  enum CmpSymmetry {
    CS_Symmetric = false,
    CS_Asymmetric = true
  };

  template <typename Cmp, CmpSymmetry asymmetric> class LazyCmp {
    public:
      typedef typename Cmp::first_argument_type first_argument_type;
      typedef typename Cmp::first_argument_type second_argument_type;
      typedef LazyBool result_type;
      LazyBool operator()(first_argument_type const& a1, second_argument_type const& a2) const {
        if (Cmp(a1,a2))
          return LB_true;
        if (asymmetric && Cmp(a2,a1))
          return LB_false;
        return LB_undefined;
      }
  };

  template <typename A, typename B> struct MultiLess {
    typedef
      Operator<
        BinaryFunctorMonad<
          LazyCmp<
            BinaryFunctorMonad<std::less<A> >,
            CS_Asymmetric>
        >, B>
      Type;
  };

  template <typename A, typename B> struct MultiEqual {
    typedef
      Operator<
        BinaryFunctorMonad<
          LazyCmp<
            BinaryFunctorMonad<std::equal_to<A> >,
            CS_Symmetric>
        >, B>
      Type;
  };

}

template <typename A, typename B> typename multioperator::MultiLess<A,B>::Type multiLess(A const& a1, A const& a2, B const& b) {
  return typename multioperator::MultiLess<A,B>::Type(a1,a2,b);
}

template <typename A, typename B> typename multioperator::MultiEqual<A,B>::Type multiEqual(A const& a1, A const& a2, B const& b) {
  return typename multioperator::MultiEqual<A,B>::Type(a1,a2,b);
}
// example: multiLess(a1,a2,multiLess(b1,b2,multiLess(c1,c2,false)))

Отказ от ответственности: я знаю BinaryFunctorMonad - это немного неправильное название, я просто не мог придумать что-то лучшее.

Версия 2: наследование

template <typename A, typename Chain> class MultiComparable {
  private:
    A const& a;
    Chain chain;
  public:
    typedef MultiComparable MultiComparableT;
    MultiComparable(A const& a, Chain chain) : a(a), chain(chain) {}
    bool operator<(MultiComparable const& as) {
      if (a != as.a)
        return a < as.a;
      return chain < as.chain;
    }
    bool operator==(MultiComparable const& as) {
      if (a != as.a)
        return false;
      return chain == as.chain;
    }
};

template <typename A, typename Chain> MultiComparable<A,Chain> multiComparable(A const& a, Chain chain) {
  return MultiComparable<A,Chain>(a,chain);
}

//example:
struct X : MultiComparable<int,MultiComparable<float,bool> > {
  int i;
  float f;
  X() : MultiComparableT(i,multiComparable(f,false)) {}
}
0 голосов
/ 16 октября 2011

Да, Boost может сделать это с помощью кортежа.

Таким образом, вы можете сделать это самостоятельно с помощью шаблонов. Но дополнительная работа для этого кажется пустой тратой времени. Просто сделай это простым способом с помощью функции (хотя мне не нравится твоя логика).

#include <boost/tuple/tuple.hpp>
#include <boost/tuple/tuple_comparison.hpp>


struct X
{
    int     a;
    float   b;
};

Стандартный способ:

#if (V == 1)

// The normal way of doing it.
bool operator<(X const& lhs, X const& rhs)
{
    if (lhs.a < rhs.a)                          {return true;}
    if ((lhs.a == rhs.a) && (lhs.b < rhs.b))    {return true;}

    // Of course for small structures (as above) it is easy to compress the above
    // lines into a single statement quickly.
    //
    // For larger structures they tend to break it out
    // until they get it correct then spend ten minutes
    // collapsing it into a single expression.
    return false;
}

Реальный нормальный способ сделать это после сжатия

#elif (V == 6)

// The normal way of doing it.
bool operator<(X const& lhs, X const& rhs)
{
    return (
                (lhs.a < rhs.a)
             || ((lhs.a == rhs.a) && (lhs.b < rhs.b))
           );
}

Как я любил, потому что это было ясно.

#elif (V == 2)

// The way I like doing it because I think it is slightly more readable.
// Though I normally use the one above now.
bool operator<(X const& lhs, X const& rhs)
{
    if (lhs.a < rhs.a)      {return true;}
    if (lhs.a > rhs.a)      {return false;}
    // If we get here the A are equal
    if (lhs.b < rhs.b)      {return true;}
    if (lhs.b > rhs.b)      {return false;}
    return false;
}

длинная версия кортежа

#elif (V == 3)

// A version that will use tupples to do it.
bool operator<(X const& lhs, X const& rhs)
{
    typedef boost::tuple<int, float>   Comp;
    Comp    l(lhs.a, lhs.b);
    Comp    r(rhs.a, rhs.b);
    return l < r;
}

короткая компактная версия кортежа

#elif (V == 4)

// A version that will use tupples but slightly more compact.
bool operator<(X const& lhs, X const& rhs)
{
    return boost::make_tuple(lhs.a, lhs.b) < boost::make_tuple(rhs.a, rhs.b);
}
#endif
...