воскресенье, 25 марта 2012 г.

Простая задачка о произведении всех элементов в массиве

Не так давно столкнулся с простой задачей: посчитать произведение элементов в массиве. Первое решение, приходящее в голову:
namespace
{
  template<class T>
  class Multiply
  {
  public:
    Multiply(T& result) : TotalResult(result)
    {
    }

    void operator()(const T& v)
    {
      TotalResult *= v;
    }

  private:
    T& TotalResult;
  };
}

void main(void)
{
  int result = 1;
  Multiply<int> functor(result);
  int buffer[] = {1, 2, 3, 4};
  std::for_each(buffer, buffer + 4, functor);
  std::cout << "Result = " << result;
}
Поскольку for_each принимает последний параметр по значению, то функтору нужно передавать ссылку извне. Также, чтобы где-то сохранять ссылку на результат нам пришлось написать класс. Однако можно решить задачу и по-другому, используя std::bind1st.
Размышления приводят к следующему коду, который не компилируется:
namespace
{
  template<class T1, class T2>
  void Multiply(T1& result, const T2& value)
  {
    result *= value;
  }
}
void main(void)
{
  int result = 1;
  int buffer[] = {1, 2, 3, 4};
  std::for_each(buffer, buffer + 4,
                std::bind1st(std::ptr_fun(Multiply<int, int>),
                             result));
  std::cout << "Result = " << result;
}
MSVS 2010 выдает ошибку где-то во внутренностях STL:
"error C2535: 'void std::binder1st<_Fn2>::operator ()(const int &) const' : member function already defined or declared". Кусок кода, который не может скомпилироваться:
result_type operator()(const argument_type& _Right) const
{// apply functor to operands
  return (op(value, _Right));
}

result_type operator()(argument_type& _Right) const
{// apply functor to operands
  return (op(value, _Right));
}
Медитация над кодом и длинной "портянкой" от компилятора проясняет следующие моменты: все дело во втором параметре const int&. Казалось бы достаточно убрать const, но это не решит проблемы. Покопавшись на flipcode, я обнаружил что проблема обсуждалась уже давно - ошибка при проектировании bind1st, bind2nd + новый стандарт С++.
Варианты решения:
  1. Использовать передачу по значению
  2. Использовать указатели
  3. Забить и использовать bind
Как оказалось bind уже "втащен" в пространство имен std::tr1. Поэтому к черту все эти недоделки и будем использовать все "блага цивилизации":
std::for_each (buffer, buffer + 4, std::bind(Multiply<int, int>, std::ref(result),
               std::placeholders::_1));
Обратите внимание на std::ref. Без этого параметр будет приниматься по значению.
Вот примерно так можно медитировать над, казалось бы, простой задачей. Хотя всего этого можно было бы избежать, если просто придерживаться правила

2 комментария:

  1. Я в плюсах не шарю, но в функциональных языках есть полезная функция reduce или foldl, которая делает как раз то, что надо. Погуглил, нашёл accumulate.
    http://ideone.com/fnaTN

    ОтветитьУдалить
  2. А ты на чем пишешь? Спасиб за ссылку, да не знал что accumulate принимает еще и кастомный функтор(=

    ОтветитьУдалить