Почему предикаты алгоритмов последовательности выполняются копией?

Мне интересно, почему функторы передаются копией в функции algorithm:

template <typename T> struct summatory
{
    summatory() : result(T()) {}

    void operator()(const T& value)
    { result += value; std::cout << value << "; ";};

    T result;
};

std::array<int, 10> a {{ 1, 1, 2, 3, 5, 8, 13, 21, 34, 55 }};
summatory<int> sum;

std::cout << "\nThe summation of: ";
std::for_each(a.begin(), a.end(), sum);
std::cout << "is: " << sum.result;

Я ожидал следующий вывод:

Суммирование: 1; 1; 2; 3; 5; 8; 13; 21; 34; 55; is: 143

Но sum.result содержит 0, то есть значение по умолчанию, назначенное в ctor. Единственный способ добиться желаемого поведения - захватить возвращаемое значение for_each:

sum = std::for_each(a.begin(), a.end(), sum);
std::cout << "is: " << sum.result;

Это происходит потому, что функтор передается с помощью for_each вместо ссылки:

template< class InputIt, class UnaryFunction >
UnaryFunction for_each( InputIt first, InputIt last, UnaryFunction f );

Таким образом, внешний функтор остается нетронутым, а внутренний (который является копией внешнего) обновляется и возвращается после выполнения алгоритма (живая демонстрация), поэтому результат будет скопирован (или перемещен) снова после выполнения всех операций.


Должна быть веская причина, чтобы сделать эту работу таким образом, но я действительно не понимаю обоснования в этом дизайне, поэтому мои вопросы:

  • Почему предикаты алгоритмов последовательности-операций передаются копией вместо ссылки?
  • Какие преимущества предлагает метод сквозной копии перед обратным ссылкой?

Ответ 1

Это в основном по историческим причинам. В 98 году, когда весь материал альго, попавший в стандартные ссылки, имел все проблемы. Это было окончательно разрешено с помощью основных и библиотечных DRs на С++ 03 и далее. Также разумные ref-wrappers и фактически работающие связывание поступают только в TR1.

Те, кто пытался использовать algos с ранним С++ 98, имеющим функции с использованием ref params или return, могут вспомнить всевозможные проблемы. Самозанятые альго также были склонны поражать страшную проблему "ссылки на ссылку".

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

Ответ 2

Это чисто догадка, но...

... позволяет на мгновение предположить, что он принимает по ссылке на const. Это означает, что все ваши члены должны быть изменчивыми, а оператор должен быть const. Это просто не кажется "правильным".

... позволяет на мгновение предположить, что он берет ссылку на не-const. Он будет называть неконстантный оператор, члены могут просто работать над штрафом. Но что, если вы хотите передать ad-hoc-объект? Как результат операции привязки (даже С++ 98 имел - уродливые и простые - инструменты привязки)? Или сам тип просто делает все, что вам нужно, и после этого вам не нужен объект, и вы просто хотите назвать его как for_each(b,e,my_functor());? Это не будет работать, поскольку временные пользователи не могут связываться с неконстантными ссылками.

Так что, может быть, это не самый лучший, но наименее плохая опция здесь, чтобы взять по стоимости, скопировать ее в процессе столько, сколько необходимо (надеюсь, не слишком часто), а затем, когда это будет сделано, верните ее из for_each. Это отлично работает с довольно низкой сложностью вашего суммирующего объекта, не требует добавления изменчивого материала, такого как метод ссылки на константу, и работает также со временными.

Но YMMV, и, скорее всего, были членами комитета, и я бы догадался, что это было в конечном итоге голосование по тому, что, по их мнению, наиболее вероятно подходит для большинства случаев использования.

Ответ 3

Возможно, это может быть обходным путем. Захватите функтор как ссылку и назовите его в лямбда

std::for_each(a.begin(), a.end(), [&sum] (T& value) 
    {
        sum(value);   
    });

std::cout << "is: " << sum.result;