Here is a lazy comparator object. It contains an arbitrary callable F , and it calls it when you call cmp(lhs, rhs) on a pair of lazy_comp_f<?> Objects, saves the results and tells who will win:
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();
Here is the factory function template helper that infers type F :
template<class F> lazy_comp_f<std::decay_t<F>> lazy_comp(F&& f){ return {std::forward<F>(f)}; }
Here is a lazy tie. It performs a number of functions that are used to produce expensive items:
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)...) ); }
Here is our main cmp . It uses < and produces a fairly efficient cmp operation. A local ADL search can find the best overload for cases where we can do it better:
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; }
Now try to resolve cmp tuples. Two assistants:
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 ) {
and we call an assistant with protection against an unrivaled number of arguments lhs / rhs:
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 ); }
now the problem is simply providing the calling files!
Inside the class do the following:
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; }
and are we done?
Please note that this solution works recursively. Anyone who overrides cmp gets the optimal version, anyone who doesn't get < . If some substructure has its own lazy based on cmp , it is called.
In C ++ 14, this is accomplished with low erase overhead. In C ++ 11, some meaningless selections have been made (to erase the type) - they can be made faster using an approach similar to delegates (low weight std::function s) or other microoptimizations.
Some used C ++ 14 functions. They are easy to implement in C ++ 11 (except for the return type auto , where I provide a workaround).