2016-08-25 2 views
6

Допустим, мы храним структура с ключом строки, и мы хотим, чтобы найти его этой строкой в ​​контейнере, как std::set, так часто реализация будет выглядеть следующим образом:Прозрачный компаратор Минимизация код

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; 
... 

Это прекрасно работает , но написание трех методов для FooComp, которые делают prety одинаковыми (логически), являются монотонными и подверженными ошибкам. Есть ли способ минимизировать этот код?

+0

с помощью шаблона? (просто случайное предположение :) – user1810087

+0

@Slava Достаточно объявить конструктор преобразования для структуры. –

+0

@ VladfromMoscow, но у этого были бы накладные расходы для нетривиальной структуры 'Foo'? Так что это не совсем равное решение.Конечно, это не значит, что это не одно из решений. Вероятно, вы должны ответить на этот вопрос. – Slava

ответ

9

Вы можете сделать следующим образом:

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; } 
}; 

Вы пишете сравнение один раз, но вы должны написать проекцию для каждого типа.

В C++ 17, он может быть даже

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; } 
}; 

И использование:

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

Demo with C++1z

+0

Кажется, что существует опечатка 'return ProjectAsId (lhs) cwschmidt

+0

@cwschmidt: Действительно, исправлено. – Jarod42

+0

Отличное решение! Есть ли способ сделать его более общим? – Slava

1

Мое решение все в классе:

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; 
    } 
}; 

Это не поддерживает t, которые могут конвертироваться в std::string.

Однако при выполнении проекции на основе сравнения, я это сделать:

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))); 
    }; 
} 

который принимает проекцию и генерирует функцию сравнения для него. Мы делаем его прозрачным с:

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)}; } 

поэтому мы можем спроектировать и быть прозрачным с помощью:

as_transparent(order_by(some_projection)); 

который только оставляет проекцию.

В C++ 14, мы просто делаем

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)); 

, который разрушает вещи вниз на модульные куски.

В C++ 17 можно использовать 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)); 

или

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

который позволяет

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

, который может быть легче читать, чем версия if constexpr. (Эта версия также может быть адаптирована к или ).

+0

'overloaded' (из [std :: visit] (http://en.cppreference.com/w/cpp/utility/variant/visit)) является альтернативой как' if constexpr'. – Jarod42

Смежные вопросы