Итерирование вывода функции-члена в std :: for_each

У меня есть класс с функцией-членом-аксессором, которую я хочу вызвать и применить результат к функтору с помощью std :: for_each. У меня есть рабочая версия ниже, в которой используются цикл for и for_each, но версия for_each загадочна и громоздка. Есть ли способ сделать версию for_each более лаконичной, учитывая, что у меня есть доступ к boost, но не к C ++ 11?

#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, но я нашел это решение, и совершенно новый вопрос не имел особого смысла.

Спасибо, вся помощь очень ценится!


person Andrew Hundt    schedule 29.02.2012    source источник
comment
Почему? Что это делает такого, чего std::accumulate(values.begin(), values.end())/values.size(); не делает?   -  person Jerry Coffin    schedule 01.03.2012
comment
Усреднение @JerryCoffin - это упрощенная задача, извлекающая суть моей реальной проблемы, заключающейся в привязке функции-члена к функтору в цикле for_each.   -  person Andrew Hundt    schedule 01.03.2012
comment
Моя точка зрения также должна была быть более широкой - я думаю, что вы принимаете неправильный подход, и вам лучше спросить о том, чего вы действительно пытаетесь достичь здесь, а не о деталях (возможно, неправильных, IMO ) путь, по которому вы начали это делать.   -  person Jerry Coffin    schedule 01.03.2012
comment
@JerryCoffin Я согласен, что это может оказаться не лучшим подходом, но, безусловно, полезно изучить некоторые тонкости C ++.   -  person Andrew Hundt    schedule 01.03.2012


Ответы (5)


Если у вас нет 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)));
person Dietmar Kühl    schedule 29.02.2012
comment
Я думаю, что это должно быть bind(avg,bind(&Value<value_type>::getValue,_1)). - person JoeG; 01.03.2012
comment
Эээ, да: я не заметил, что Value - это шаблон. Кроме того, может потребоваться адрес avg в дополнение к указанию аргумента шаблона. Я обновлю ответ ... - person Dietmar Kühl; 01.03.2012
comment
Это не сработало, но подсказало мне, как найти рабочее решение. Я надеюсь, что есть более простой способ, чем моя версия, поэтому я изменил вопрос и пока оставляю его открытым. - person Andrew Hundt; 01.03.2012

если вы используете С ++ 11, вы можете попробовать

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

or

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
}

Попытки быть слишком умными с функциональным объектом и т.п. часто могут иметь обратный эффект - затемнять ваш код.

person 111111    schedule 29.02.2012
comment
Обычно рекомендуется увеличивать итераторы с помощью оператора приращения префикса, например ++it. - person Greg Hewgill; 01.03.2012
comment
@GregHewgill соглашается, я этого не видел. фиксированный - person 111111; 01.03.2012
comment
также -> должен быть . (a и wt являются ссылками, а не итераторами) - person Philipp; 01.03.2012
comment
@NicolBolas: а какая часть отредактированных 11 часов, вы не понимаете, это дополнение к его вопросу было добавлено через 3 часа после того, как я ответил - я этого не знал. - person 111111; 01.03.2012

Один из способов сделать его более аккуратным - использовать 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()));
person Paul Fultz II    schedule 02.03.2012
comment
Очень интересная техника. Мне нужно время, чтобы понять Boost.Phoenix. Спасибо! - person Andrew Hundt; 05.03.2012

Благодарим Матиаса Гаунара из списка рассылки boost.users за то, что он указал мне на это решение:

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

Обертывание avg с boost::ref требуется, потому что в противном случае копия 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;
}
person Andrew Hundt    schedule 01.03.2012

Если вы можете использовать boost, но не функции C ++ 11, я бы подумал об использовании макрос BOOST_FOREACH

Да, это макрос, но что касается макросов, он ведет себя хорошо

Он также довольно хорошо читается, и в нем трудно ошибиться.

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

Диапазон C ++ 11, основанный на циклах, заменит его

person matburton    schedule 03.03.2012