C ++与优雅语法的深度懒惰比较?

时间:2015-05-07 14:52:50

标签: c++ c++11

我有一个C ++类,我需要定义一个比较器,它应该考虑几种可能很昂贵的方法的结果。我不想为我的集合中的所有对象缓存这些方法的结果,因为具有最高优先级的标准更便宜,并且我希望底部非常昂贵的标准仅在极少数情况下触发。

如果我有一个cmp()函数,当第一个参数小于,等于或大于第二个参数时返回-1,0或1,并且使用保留整数的快捷逻辑运算符,我可以轻松地写< / p>

int compare(const Class &rhs) const {
    return cmp(expensive_method_a(), rhs.expensive_method_b()) ||
           cmp(expensive_method_b(), rhs.expensive_method_b()) ||
           ...
}

不幸的是我需要使用&lt;操作员,因此变得丑陋,昂贵且容易出错:

bool operator<(const Class &rhs) const {
    return expensive_method_a() < rhs.expensive_method_a() ||
           (expensive_method_a() == rhs.expensive_method_a() &&
            (expensive_method_b() < rhs.expensive_method_b() ||
             (expensive_method_b() == rhs.expensive_method_b() &&
              (...
           ))))
}

或者,成本更低但仍然相当丑陋:

bool operator<(const Class &rhs) const {
    auto al = expensive_method_a(), ar = rhs.expensive_method_a();
    if (al != ar) return al < ar;
    auto bl = expensive_method_b(), br = rhs.expensive_method_b();
    if (bl != br) return bl < br;

我在This其他问题上读过关于std :: tie的内容,但是如果我理解正确的话,领带会在开始比较之前评估我的所有方法,并且我希望这些参数被懒惰地评估

我考虑过定义一个预处理器宏,例如:

#define CUT_COMPARE(a,b) { auto _x = (a); auto _y = (b); if (_x != _y) return (_x < _y); }

我会使用:

bool operator<(const Class &rhs) const {
    CUT_COMPARE(expensive_method_a(), rhs.expensive_method_a());
    CUT_COMPARE(expensive_method_b(), rhs.expensive_method_b());
    ...
}

希望大括号将我的_x_y括在私有范围内,但唉,clang++抱怨_x_y的多个定义

有更漂亮的方法吗?

4 个答案:

答案 0 :(得分:4)

您可以将要调用的所有成员函数转发给帮助模板,该模板会根据需要遍历它们:

bool operator<(const Class& rhs) const {
    return lazy_compare(*this, rhs, &Class::expensive_1,
                                    &Class::expensive_2,
                                    &Class::expensive_3);
}   

lazy_compare可变参数函数将根据需要一次一个地遍历那些指向成员的函数。基本案例只是true

template <typename T, typename... MFs>
bool lazy_compare(const T&, const T&, MFs...) {
    return true;
}

递归的情况是弹出第一个指向成员的指针,看看我们是否可以停在那个指针上:

template <typename T, typename R, typename... MFs>
bool lazy_compare(const T& left, const T& right, R (T::*mf)() const, MFs... rest) {
    R vleft = (left.*mf)(), vright = (right.*mf)();
    if (vleft != vright) {
        return vleft < vright;
    }   
    else {
        return lazy_compare(left, right, rest...);
    }   
}

答案 1 :(得分:2)

这是一个懒惰的比较器对象。它包含一些任意的可调用F,当你在一对cmp(lhs, rhs)对象上调用lazy_comp_f<?>时,它会调用它,存储结果,并告诉你谁赢了:

template<class F>
struct lazy_comp_f {
  F f;
  template<class F1, class F2>
  friend int cmp( lazy_comp_f<F1>const& lhs, lazy_comp_f<F2>const& rhs) {
    auto l = lhs.f();
    auto r = rhs.f();
    // using cmp_ns::cmp; here
    return cmp(l,r);
  }

  // ctors
  lazy_comp_f(F&& fin):f(std::forward<F>(fin)) {}
  lazy_comp_f(lazy_comp_f&&)=default;
  lazy_comp_f(lazy_comp_f const&)=default;
  template<class O, class=std::enable_if_t<std::is_convertible<O const&,F>>>
  lazy_comp_f(lazy_comp_f<O> const&o):f(o.f){}
  template<class O, class=std::enable_if_t<std::is_convertible<O,F>>>
  lazy_comp_f(lazy_comp_f<O>&&o):f(std::move(o).f){}
};
template<class T>
using lazy_comp_t = lazy_comp_f<std::function<T()>>;

这是一个模板工厂函数助手,可以扣除F类型:

template<class F>
lazy_comp_f<std::decay_t<F>>
lazy_comp(F&& f){ return {std::forward<F>(f)}; }

这是一个懒惰的领带。它需要一系列用于生产昂贵物品的功能:

template<class...Fs, class R=std::tuple< lazy_comp_f<std::decay_t<Fs>>... >>
R lazy_tie( Fs&& fs ) {
  return R( lazy_comp(std::forward<Fs>(fs)...) );
}

这是我们的基本cmp。它使用<并产生合理有效的cmp操作。对于我们可以做得更好的情况,本地ADL查找可以找到更好的重载:

template<class T, class U>
int cmp( T const& lhs, U const& rhs ) {
  if (lhs < rhs) return -1;
  if (rhs < lhs) return 1;
  return 0;
}

现在努力允许cmp元组。两位助手:

namespace details {
  template<class...Ts, class...Us>
  int cmp(
    std::index_sequence<>,
    std::tuple<Ts...> const& lhs,
    std::tuple<Us...> const& rhs
  ) {
    return 0;
  }
  template<size_t I, size_t...Is,class...Ts, class...Us>
  int cmp(
    std::index_sequence<I, Is...>,
    std::tuple<Ts...> const& lhs,
    std::tuple<Us...> const& rhs
  ) {
    // maybe using comp_ns::cmp here?
    int c = cmp( std::get<I>(lhs), std::get<I>(rhs) );
    if (c!=0) return c;
    return cmp(std::index_sequence<Is...>{}, lhs, rhs);
  }
}

我们称之为助手,防御无法匹敌的lhs / rhs args:

template<class...Ts, class...Us>
std::enable_if_t<sizeof...(Ts)==sizeof...(Us), int>
cmp(
  std::tuple<Ts...> const& lhs,
  std::tuple<Us...> const& rhs
) {
  return details::cmp( std::make_index_sequence<sizeof...(Ts)>{}, lhs, rhs );
}

现在的问题是只提供callables!

class内执行以下操作:

auto lazy_comparer() const
// std::tuple< lazy_comp_t<A>, lazy_comp_t<B>, lazy_comp_t<C> > in C++11
// where `A`, `B` and `C` are the return types of expensive_method_a etc
{
  return lazy_tie(
    [=]{ return expensive_method_a(); },
    [=]{ return expensive_method_b(); },
    [=]{ return expensive_method_c(); }
    // etc
  );
}
friend int cmp( Class const& lhs, Class const& rhs ) {
  // using namespace cmp_ns::cmp here
  return cmp( lhs.lazy_comparer(), rhs.lazy_comparer() ) < 0;
}
friend bool operator<( Class const& lhs, Class const& rhs ) {
  return cmp(lhs,rhs)<0;
}

我们完成了吗?

请注意,此解决方案以递归方式工作。覆盖cmp的任何人都会获得最佳版本,任何未获得基于<的人都会获得最佳版本。如果某个子结构有自己的基于lazy的{​​{1}},则会调用它。

在C ++ 14中,这是通过低类型擦除开销来完成的。在C ++ 11中,完成了一些无意义的分配(用于类型擦除) - 使用类似委托的方法(轻量级cmp s)或其他微优化,可以更快地完成它们。

使用了一些C ++ 14功能。它们很容易在C ++ 11中实现(除了std::function返回类型,我提供了一种解决方法)。

答案 2 :(得分:0)

我会坚持使用漂亮的比较方法,这样写:

int compare(const Class &rhs) const {
    int cr;
    cr = cmp(expensive_method_a(), rhs.expensive_method_a());
    if (cr != 0) return cr;
    cr = cmp(expensive_method_b(), rhs.expensive_method_b());
    if (cr != 0) return cr;
    ...
}

这样,只要一个方法给出不同的结果,它就会返回正确的符号,并且只有在相等的情况下才会返回到最后。

您可以直接在所有比较器中使用它:

bool operator<(const Class &rhs) const {
    return compare(rhs) < 0;
}
bool operator<=(const Class &rhs) const {
    return compare(rhs) <= 0;
}
bool operator>(const Class &rhs) const {
    return compare(rhs) > 0;
}
bool operator>=(const Class &rhs) const {
    return compare(rhs) >= 0;
}
bool operator==(const Class &rhs) const {
    return compare(rhs) == 0;
}
bool operator!=(const Class &rhs) const {
    return compare(rhs) != 0;
}

答案 3 :(得分:-4)

你可以像这样实现它:

bool operator<(const Class &rhs) const {
    return expensive_method_a() < rhs.expensive_method_a() ||
           expensive_method_b() < rhs.expensive_method_b() ||
           ..
           expensive_method_N() < rhs.expensive_method_N() ||
}

只要其中一个方法的计算结果为true,它就会返回,而不会评估其他方法。

相关问题