【问题标题】:Iterating over the output of a member function in std::for_each迭代 std::for_each 中成员函数的输出
【发布时间】:2012-03-19 10:52:21
【问题描述】:

我有一个带有访问器成员函数的类,我想调用它并将结果应用到使用 std::for_each 的仿函数。我在下面有一个使用 for 循环和 for_each 的工作版本,但 for_each 版本既神秘又麻烦。考虑到我可以使用 boost 而不是 C++11,有没有办法可以使 for_each 版本更简洁?

#if 0
   // for loop version:
   for(value_vector_type::iterator it = values.begin(); it!=values.end(); it++){
     avg(it->getValue());  // I want to put this in a for_each loop
   }
#else
  //  bind version:
  std::for_each(values.begin(), values.end(), // iterate over all values
    boost::bind(
      boost::mem_fn(&average_type::operator()), // attach the averaging functor to the output of the getvalue call
      &avg, 
      boost::bind(
        boost::mem_fn(&value_wrapper_type::getValue), // bind the getValue call to each element in values
        _1
      )
    )
  );
#endif    

这是完整的工作实现:

#include <vector>
#include <algorithm>
#include <iostream>
#include <boost/bind.hpp>
#include <boost/bind/mem_fn.hpp>

// A value wrapper
template<typename T>
struct Value {
  Value(){}
  Value(const T& value, bool valid = true):m_value(value),m_valid(valid){}

  T getValue(){ return m_value; }
  bool getValid(){ return m_valid; }
  void setValue(const T& value){ m_value = value; }
  void setValid(const T& valid){ m_valid = valid; }

private:
  T m_value;
  bool m_valid;   
};

// Class that calculates the average piecewise
template<typename T>
struct Average {
private:
    T m_numPoints;
    T m_ChannelSum;

public:

    Average() : m_numPoints(0), m_ChannelSum(0.0){}

    void operator()(T value){
        m_numPoints++;
        m_ChannelSum+=value;
    }

    double getAverage(){ return m_ChannelSum/m_numPoints; }
    T getCount(){ return m_numPoints; }
    T getSum(){ return m_ChannelSum; }
};

// Run the average computation on several values
int main(int argc, char** argv){
  typedef int value_type;
  typedef Value<value_type> value_wrapper_type;
  typedef std::vector<value_wrapper_type> value_vector_type;
  value_vector_type values;
  values.push_back(value_wrapper_type(5));
  values.push_back(value_wrapper_type(7));
  values.push_back(value_wrapper_type(3));
  values.push_back(value_wrapper_type(1));
  values.push_back(value_wrapper_type(2));

  typedef Average<value_type> average_type;
  average_type avg;

#if 0
   // for loop version:
   for(value_vector_type::iterator it = values.begin(); it!=values.end(); it++){
     avg(it->getValue());  // I want to put this in a for_each loop
   }
#else
  //  bind version:
  std::for_each(values.begin(), values.end(), // iterate over all values
    boost::bind(
      boost::mem_fn(&average_type::operator()), // attach the averaging functor to the output of the getvalue call
      &avg, 
      boost::bind(
        boost::mem_fn(&value_wrapper_type::getValue), // bind the getValue call to each element in values
        _1
      )
    )
  );
#endif    
  std::cout << "Average: " << avg.getAverage() << " Count: " << avg.getCount() << " Sum: " << avg.getSum() << std::endl;
}

注意:我最初的问题是如何构建一个 for_each,但我发现这个解决方案和一个全新的问题没有多大意义。

谢谢,非常感谢所有帮助!

【问题讨论】:

  • 为什么?这完成了什么std::accumulate(values.begin(), values.end())/values.size(); 不会做的事情?
  • @JerryCoffin Averaging 是一个简化的问题,它提取了我实际问题的本质,即在 for_each 循环中将成员函数绑定到函子。
  • 我的观点也意味着更广泛——我认为你采取了错误的方法,你最好问问你在这里真正想要完成什么,而不是您为实现这一目标而开始采取的(可能是错误的,IMO)路径的详细信息。
  • @JerryCoffin 我同意这可能不是最好的方法,但学习一些复杂的 C++ 肯定是有益的。

标签: c++ boost foreach iterator member-functions


【解决方案1】:

如果你用的是c++11那么你可以试试

for(auto& a: values)
    avg(a->getValue());

std::for_each(a.begin(), a.end(), [](whatever_type& wt){
    avg(wt->getValue());
});

如果你不是,那么我认为这个玩具和你想得到的一样好,尽管格式化不会有坏处。

for(value_vector_type::iterator it = values.begin(); 
    it!=values.end(); 
    ++it)
{
    avg(it.getValue());  // I want to put this in a for_each loop
}

对函数对象等过于聪明往往会产生模糊代码的相反效果。

【讨论】:

  • 使用前缀递增运算符递增迭代器通常是个好主意,如++it
  • @GregHewgill 同意我没看到。固定
  • -&gt; 也应该是 .awt 是引用而不是迭代器)
  • @NicolBolas:你不明白“编辑 11 小时”的哪一部分,在我回复后 3 小时添加了他的问题——我没有这些知识。
【解决方案2】:

如果您没有 C++11 但使用 Boost,您可以尝试使用 bind() 表达式(这也适用于 C++2011,因为 bind() 是 C++2011 的一部分):

std::for_each(a.begin(), a.end(), bind(&avg<value_type>, bind(&Value<value_type>::getValue, _1)));

【讨论】:

  • 我认为应该是bind(avg,bind(&amp;Value&lt;value_type&gt;::getValue,_1))
  • 呃,是的:我没有注意到Value 是一个模板。此外,除了指定模板参数之外,可能还需要获取avg 的地址。我会更新答案...
  • 这不起作用,但给了我一个寻找可行解决方案的提示。我希望有比我的版本更简单的方法,所以我修改了这个问题,我暂时保持开放。
【解决方案3】:

感谢 boost.users 邮件列表上的 Mathias Gaunard 为我指出了这个解决方案:

  std::for_each(values.begin(), values.end(),
    boost::bind(boost::ref(avg), boost::bind(&value_wrapper_type::getValue, _1))
  );

需要用boost::ref 包装avg,否则avg 的副本将填写getValue() 的结果,而不是avg 本身。

这是完整的编译和测试解决方案:

#include <stdexcept>
#include <vector>
#include <algorithm>
#include <iostream>
#include <boost/bind.hpp>
#include <boost/bind/mem_fn.hpp>

// A value wrapper
template<typename T>
struct Value {
  Value(){}
  Value(const T& value, bool valid = true):m_value(value),m_valid(valid){}

  T getValue(){ return m_value; }
  bool getValid(){ return m_valid; }
  void setValue(const T& value){ m_value = value; }
  void setValid(const T& valid){ m_valid = valid; }

private:
  T m_value;
  bool m_valid;   
};

// Class that calculates the average piecewise
template<typename T>
struct Average {
private:
    T m_numPoints;
    T m_ChannelSum;

public:
  typedef void result_type;

    Average() : m_numPoints(0), m_ChannelSum(0.0){}

    result_type operator()(T value){
        m_numPoints++;
        m_ChannelSum+=value;
    }

    double getAverage(){ 
    if (m_ChannelSum==0) {
      throw std::logic_error("Cannot get average of zero values");
    }

    return m_ChannelSum/m_numPoints; 
  }
    T getCount(){ return m_numPoints; }
    T getSum(){ return m_ChannelSum; }
};

// Run the average computation on several values
int main(int argc, char** argv){
  typedef int value_type;
  typedef Value<value_type> value_wrapper_type;
  typedef std::vector<value_wrapper_type> value_vector_type;
  value_vector_type values;
  values.push_back(value_wrapper_type(5));
  values.push_back(value_wrapper_type(7));
  values.push_back(value_wrapper_type(3));
  values.push_back(value_wrapper_type(1)); 
  values.push_back(value_wrapper_type(2));

  typedef Average<value_type> average_type;
  average_type avg;

#if 0
  // for loop version:
  for(value_vector_type::iterator it = values.begin(); it!=values.end(); it++){
   avg(it->getValue());  // I want to put this in a for_each loop
  }
#else
  //  bind version:
  std::for_each(values.begin(), values.end(),
    boost::bind(boost::ref(avg), boost::bind(&value_wrapper_type::getValue, _1))
  );
#endif    
  std::cout << "Average: " << avg.getAverage() << " Count: " << avg.getCount() << " Sum: " << avg.getSum() << std::endl;
}

【讨论】:

    【解决方案4】:

    使它看起来更整洁的一种方法是使用Boost.Phoenix。您可以缩短为:

    std::for_each(values.begin(), values.end(), lazy(avg)(arg1.getValue()));
    

    这里是如何做到这一点。您需要做的第一件事是使avg 函数对象变得惰性。最简单的方法是就地使用函数,定义如下:

    template<class Function>
    function<Function> lazy(Function x)
    {
        return function<Function>(x);
    }
    

    接下来你需要为 getValue 编写一个函数对象,它可以是惰性的,像这样:

    struct get_value_impl
    {
        // result_of protocol:
        template <typename Sig>
        struct result;
    
        template <typename This, typename T>
        struct result<This(Value<T>&)>
        {
            // The result will be T
            typedef typename T type;
        };
    
        template <typename V>
        typename result<get_value_impl(V &)>::type
        operator()(V & value) const
        {
            return value.getValue();
        }
    };
    

    第三,我们扩展了凤凰演员,使用我们的get_value_impl类,所以它会有一个getValue方法,像这样:

    template <typename Expr>
    struct value_actor
        : actor<Expr>
    {
        typedef actor<Expr> base_type;
        typedef value_actor<Expr> that_type;
    
        value_actor( base_type const& base )
            : base_type( base ) {}
    
        typename expression::function<get_value_impl, that_type>::type const
        getValue() const
        {
            function<get_value_impl> const f = get_value_impl();
            return f(*this);
        }
    };
    

    最后,我们通过定义参数并将其传递给 for_each 算法将它们放在一起:

    expression::terminal<phoenix::argument<1>, value_actor>  arg1;
    std::for_each(values.begin(), values.end(), lazy(avg)(arg1.getValue()));
    

    【讨论】:

    • 非常有趣的技术。我将需要一些时间来了解 Boost.Phoenix。谢谢!
    【解决方案5】:

    如果你可以使用 boost,但不能使用 C++11 功能,那么我会考虑使用the BOOST_FOREACH macro

    是的,它是一个宏,但随着宏的使用,它的表现很好

    它读起来也很好,很难出错

    BOOST_FOREACH(const Value& rValue, values)
    {
        avg(rValue.getValue());
    }
    

    基于 C++11 范围的 for 循环将替换它

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-01-07
      • 2012-04-16
      • 1970-01-01
      • 2011-06-30
      • 1970-01-01
      • 2019-02-27
      相关资源
      最近更新 更多