2016-08-25 5 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; 
... 

これは正常に動作しますしかし、同じ(論理的に)一致する3つのメソッドは、単調でエラーが発生しやすいFooCompの3つのメソッドを記述します。そのコードを最小限にする方法はありますか?

+0

テンプレートを使用していますか? (ちょっとランダムな推測:) – user1810087

+0

@ Slava構造体のコンバージョンコンストラクタを宣言すれば十分です。 –

+0

@VladfromMoscowしかし、それは単純ではない構造体 'Foo'のためのオーバーヘッドを持っていますか?したがって、これは全く同じソリューションではありません。もちろん、これは解決策の1つではありません。おそらくあなたはそれを答えにするべきです。 – 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)); 

またはif constexprバージョンよりも読みやすいかもしれ

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

を許可

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

。 (このバージョンはまたはにも適用できます)。

+0

'overloaded'([std :: visit](http://en.cppreference.com/w/cpp/utility/variant/visit))は' if constexpr'の代替手段です。 – Jarod42

関連する問題