【问题标题】:std algorithms with pointer to member as comparator/"key"将成员指针作为比较器/“键”的 std 算法
【发布时间】:2014-06-29 02:18:12
【问题描述】:

我经常发现自己将std::sortstd::max_element 等与简单调用成员函数的 lambda 结合使用

std::vector<MyType> vec;
// populate...
auto m = std::max_element(std::begin(vec), std::end(vec),
    [](const MyType& a, const MyType& b) { return a.val() < b.val()})

这感觉像是在浪费字符和清晰度。我知道我可以编写另一个函数/可调用函数并将函数指针/可调用对象传递给这些算法函数,但我经常需要在程序中只进行一次排序,这对我来说并不好解决问题的方法。理想情况下,我想做的是:

auto m = std::max_element(std::begin(vec), std::end(vec), &MyType::val);

并让对象按它们的val()s 排序。我忽略的标准库的某些部分是否可以帮助我解决这个问题?或另一种简单的方法?我想让排序或搜索的内容尽可能明显。

我知道仅&amp;MyType::val 是不够的,我正在寻找可以包装它的东西,或者提供类似的功能而不会模糊其含义。

【问题讨论】:

    标签: c++ algorithm c++11 pointer-to-member


    【解决方案1】:

    为了避免需要使用std::mem_fn,对sehes 答案的改进是为compare_by 函数提供指向成员重载的指针。

    示例用法

    std::sort(std::begin(vec), std::end(vec), compare_by(&MyType::field));
    std::sort(std::begin(vec), std::end(vec), compare_by(&MyType::field, std::greater<>{}));
    

    要实现的代码

    #include <functional> // std::less
    #include <utility> // std::move
    #include <type_traits> // std::is_invocable_r
    // Forward declaration
    template<typename R, typename T, typename F = std::less<R>>
    auto compare_by(R T::*, F = F{});
    
    // Implementation
    namespace detail {
    template<typename T, typename F>
    struct compare_by_t;
    
    template<typename R, typename T, typename F>
    struct compare_by_t<R T::*, F> : private F
    {
        compare_by_t(F&& f, R T::*m): F{std::move(f)}, _member{m} {}
        R T::* _member;
        bool operator()(T const& x, T const& y) const
        {
            return F::operator()(x .* _member, y .* _member);
        }
    };
    } // detail
    
    template<typename R, typename T, typename F>
    auto compare_by(R T::* member, F f)
    {
        static_assert(std::is_invocable_r<bool, F, R, R>::value);
        return detail::compare_by_t<R T::*, F>{ std::move(f), member };
    }
    

    【讨论】:

      【解决方案2】:

      您可以做到without 引入任何新功能(模板化或非模板化)。

      Just 使用bindstd::less

      auto m = std::max_element(vec.begin(), vec.end(), 
          bind(less<>(), bind(&MyType::val, _1), bind(&MyType::val, _2)));
      

      【讨论】:

      • less&lt;&gt; 是 C++1y 特性。
      • @dyp 在 VS2013 中工作,将其设为 less&lt;int&gt;,它将在 g++4.8 中工作
      • 是的,这是 MSVC 支持“提前”的(少数)功能之一,例如 make_unique。 IIRC 都是我的微软员工提出的。
      • 好点。我爱std::bind。但是,在这种情况下,lambda 显然更胜一筹(这仍然重复相同的部分)。此外,功能组合的嵌套绑定可能既笨拙又令人惊讶(stackoverflow.com/q/18519087/85371boost::apply)。不过,+1 提供信息。 PS。不需要val() 成员,我认为std::bind handles p-t-m just fine
      【解决方案3】:

      您可以使用std::mem_fn(或std::tr1::mem_fn

      int main()
      {
          std::vector<MyType> vec;
      
          auto m = std::max_element(std::begin(vec), std::end(vec), compare_by(std::mem_fn(&MyType::field)));
      }
      

      当然,这假设您的工具箱中有一个类似compare_by 的实用程序(您应该这样做:)):

      template <typename F>
      struct CompareBy {
          explicit CompareBy(F&& f) : f(std::forward<F>(f)) {}
          template <typename U, typename V> 
              bool  operator()(U const& u, V const& v) const {
                  return f(u) < f(v);
              }
      
      private:
          F f;
      };
      
      template <typename F>
      CompareBy<F> compare_by(F&& f) { return CompareBy<F>(std::forward<F>(f)); }
      

      Live On Coliru

      【讨论】:

      • 好的,这是一个有价值的选择,但是 (@Ryan) 请告诉我为什么这应该比单行 lambda 更好?
      • 它的意思是这样的(在阅读您的建议之前我已经更正了它)......我在这里问它,因为我猜他是支持者之一。
      • @sehe 当然,我只是停留在上下文中,并认为不使用 lambdas 是一个先决条件。我也会使用一个,除非我有 std::algorithms 交替使用诸如 less / greater / greater_equal 等操作,std:: 让我的生活更轻松
      • 这里转发是无意义的。与什么都不做相比,移动不会增加任何效率,这是您通过引用传递时得到的。在 C++11 之前的版本中没有副本。为什么要向它添加动作?
      • 在库代码中尽可能完美转发可能是一个“好”的习惯。但如果可能的话,想想自己在做什么可能是一个“更好”的习惯。
      【解决方案4】:

      为您的自定义类型重载operator&lt; 怎么样?这可以在类内部(或直接在它旁边)自然地完成,然后在迭代器之外不需要进一步的参数。

      传递你的val() 函数是不可能的,因为你必须传递一个二元运算符。

      编辑:在阅读了其他有价值的替代方案(也是很好的回应)之后,我想确认我在下面的评论中已经提到的内容:在我看来,没有什么比 lambda 表达式的可读性、局部性和灵活性更好的了(- -关于将某些段落写两次的风险)。

      @Ryan Haining:我建议您保留原帖中的内容。

      【讨论】:

      • 因为说某事小于另一个通常是没有意义的。如果我想按薪水对员工进行排序,那将意味着重载operator&lt; 以按薪水进行比较。这没有表达力,因为说一个人“小于”另一个人并不直观,而且如果我以后需要按年龄对他们进行排序,我又回到了原点。
      • 那么我猜 lambda 是你能想到的最短的可能性。当然,您也可以以某种方式将 std::bindstd::less 堆叠在一起,但这将更难阅读,imo。
      【解决方案5】:

      模板化比较器可以帮助您:

      template <typename StructureType,
                typename MemberType,
                MemberType StructureType::*member>
      bool comparator(const StructureType& the_first, const StructureType& the_second)
      {
        return the_first.*member < the_second.*member;
      }
      

      http://ideone.com/K8ytav

      一点类型特征的魔法当然可以让你避免编写类型。

      【讨论】:

        猜你喜欢
        • 2018-10-18
        • 1970-01-01
        • 2013-01-05
        • 1970-01-01
        • 2012-01-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多