2016-08-25 19 views
6

Powiedzmy przechowujemy struct z kluczem smyczkowy i chcemy go znaleźć przez ten ciąg w pojemniku jak std::set, więc wspólna realizacja będzie wyglądać następująco:Transparent komparator minimalizacja kod

struct Foo { 
    std::string id; 
}; 

struct FooComp { 
    using is_transparent = std::true_type; 

    bool operator()(const Foo &foo, const std::string &str) const 
    { 
     return foo.id < str; 
    } 

    bool operator()(const std::string &str, const Foo &foo) const 
    { 
     return str < foo.id; 
    } 

    bool operator()(const Foo &foo1, const Foo &foo2) const 
    { 
     return foo1.id < foo2.id; 
    } 
}; 

std::set<Foo,FooComp> foo_set; 
... 

To działa prawidłowo , ale pisanie trzech metod dla FooComp, które mają prety pasują do tego samego (logicznie), jest monotoniczne i podatne na błędy. Czy istnieje sposób na zminimalizowanie tego kodu?

+0

przy użyciu szablonu? (tylko losowe domysły :) – user1810087

+0

@Slava Wystarczy zadeklarować konstruktor konwersji dla struktury. –

+0

@VladfromMoscow ale to miałoby narzut na niebanalną strukturę 'Foo' prawda? To nie jest dokładnie takie samo rozwiązanie.Oczywiście nie oznacza to, że nie jest to jedno z rozwiązań. Prawdopodobnie powinieneś to zrobić. – Slava

Odpowiedz

9

można zrobić, jak następuje:

struct Foo { 
    std::string id; 
}; 

struct FooComp { 
    using is_transparent = std::true_type; 

    template <typename LHS, typename RHS> 
    bool operator()(const LHS& lhs, const RHS& rhs) const 
    { 
     return ProjectAsId(lhs) < ProjectAsId(rhs); 
    } 

private: 
    const std::string& ProjectAsId(const std::string& s) const { return s; } 
    const std::string& ProjectAsId(const Foo& foo) const { return foo.id; } 
}; 

Piszesz porównania raz, ale trzeba napisać projekcję dla każdego typu.

W C++ 17, może to być nawet

template <auto f> struct ProjLess 
{ 
    using is_transparent = std::true_type; 

    template <typename LHS, typename RHS> 
    bool operator()(const LHS& lhs, const RHS& rhs) const 
    { 
     return project(lhs) < project(rhs); 
    } 

private: 
    template <typename T> 
    using f_t = decltype(std::invoke(f, std::declval<const T&>())); 

    template <typename T> 
    using is_f_callable = is_detected<f_t, T>; 

    template <typename T, std::enable_if_t<is_f_callable<T>::value>* = nullptr> 
    decltype(auto) project(const T& t) const { return std::invoke(f, t); } 

    template <typename T, std::enable_if_t<!is_f_callable<T>::value>* = nullptr> 
    const T& project(const T& t) const { return t; } 
}; 

i użytkowania:

std::set<Foo, ProjLess<&Foo::id>> s; 

Demo with C++1z

+0

Wydaje się, że istnieje literówka 'return ProjectAsId (lhs) cwschmidt

+0

@ cwschmidt: Rzeczywiście, naprawione. – Jarod42

+0

Świetne rozwiązanie! Czy istnieje sposób na uczynienie go bardziej ogólnym? – Slava

1

Moje rozwiązanie jest wszystko w klasie:

struct FooComp { 
    using is_transparent = std::true_type; 
    struct FooProj { 
    std::string const& str; 
    FooProj(std::string const& sin):str(sin) {} 
    FooProj(const Foo& foo):str(foo.id) {} 
    FooProj(FooProj const&) = default; 
    friend bool operator<(FooProj lhs, FooProj rhs) { 
     return lhs.str < rhs.str; 
    } 
    }; 
    bool operator()(FooProj lhs, FooProj rhs) const 
    { 
    return lhs<rhs; 
    } 
}; 

To nie jest suppor typy, które można przekonwertować na std::string.

Jednak, gdy robi porównanie projekcji opartej zrobić to:

template<class F, class After=std::less<>> 
auto order_by(F&& f, After&& after={}) { 
    return 
    [f=std::forward<F>(f), after=std::forward<After>(after)] 
    (auto&& rhs, auto&&lhs)->bool { 
     return after(f(decltype(lhs)(lhs)), f(decltype(rhs)(rhs))); 
    }; 
} 

która odbywa projekcję i generuje funkcję porównawczą dla niego. Robimy to przezroczysty z:

template<class F> 
struct as_transparent_t { 
    F f; 
    using is_transparent=std::true_type; 
    template<class Lhs, class Rhs> 
    bool operator(Lhs const& lhs, Rhs const& rhs)const{ return f(lhs, rhs); } 
}; 
template<class F> 
as_transparent_f<std::decay_t<F>> 
as_transparent(F&& f) { return {std::forward<F>(f)}; } 

więc możemy wystawać i być przejrzysty poprzez:

as_transparent(order_by(some_projection)); 

których tylko pozostawia projekcji.

w C++ 14 po prostu zrobić

std::string const& foo_proj_f(std::string const& str) { return str; } 
std::string const& foo_proj_f(Foo const& foo) { return foo.id; } 
auto foo_proj = [](auto const& x)->decltype(auto){ return foo_proj_f(x); }; 
auto foo_order = as_transparent(order_by(foo_proj)); 

który łamie rzeczy w dół na kawałki modułowych.

w C++ 17 możemy użyć if constexpr:

auto foo_proj = [](auto const& x)->std::string const& { 
    if constexpr(std::is_same<decltype(x), std::string const&>{}) { 
    return x; 
    } 
    if constexpr(std::is_same<decltype(x), Foo const&>{}) { 
    return x.id; 
    } 
}; 
auto foo_order = as_transparent(order_by(foo_proj)); 

lub

template<class...Ts> 
struct overloaded:Ts...{ 
    using Ts::operator()...; 
    overloaded(Ts...ts):Ts(std::move(ts)...){} 
}; 
template<class...Ts> overloaded -> overloaded<Ts...>; 

który pozwala

auto foo_proj = overloaded{ 
    [](std::string const& s)->decltype(auto){return s;}, 
    [](Foo const& f)->decltype(auto){return f.id;} 
}; 

które mogą być łatwiejsze do odczytania niż wersja if constexpr. (Ta wersja może być również dostosowana do lub ).

+0

'overloaded' (od [std :: visit] (http://en.cppreference.com/w/cpp/utility/variant/visit)) jest alternatywą jako' if constexpr'. – Jarod42

Powiązane problemy