Почему в стандартном вызове stl алгоритма происходит передача функтора по значению?
Возник вопрос - почему, в приведенной сигнатуре, предикат передается по значению:(https://ru.cppreference.com/w/cpp/algorithm/count)
template< class InputIt, class UnaryPredicate >
typename iterator_traits<InputIt>::difference_type
count_if( InputIt first, InputIt last, UnaryPredicate p );
Мейерс в "Эффективное использование STL" писал, что такой способ передачи дает две проблемы:
- Расходы при копировании больших объектов
- Отсутствие полиморфизма для объектов, которые мы передали по значению
Эти проблемы решаются тем, что мы создаем оболочку - функтор с указателем на тяжелый объект с виртуальными функциями.
Однако Мейерс не отвечает почему вообще в стандарте в данном месте присутствует передача по значению.
Почему изначально не вшита передача по указателю?
UPD: Пример того, что получается передавать через указатель необходимые объекты:
namespace ustd //user std
{
template <typename InputIt, typename UnaryPredicate>
typename std::iterator_traits<InputIt>::difference_type
count_if(InputIt first, InputIt last, UnaryPredicate* predicate)
{
typename std::iterator_traits<InputIt>::difference_type count = 0;
while (first != last)
if ((*predicate)(*first++))
++count;
return count;
}
};
//проверяем
bool predicate(int i)
{
return i > 2;
}
class Functor
{
public:
explicit Functor(int i)
: i(i)
{}
bool operator()(int var) const
{
return var > i;
}
private:
int i = 0;
};
int main()
{
std::vector<int> v = { 1, 2, 3, 4, 2, 3 }; //size = 6
std::cout << ustd::count_if(v.begin(), v.end(), &Functor(2)); //вывод 3
std::cout << ustd::count_if(v.begin(), v.end(), predicate); //вывод 3
std::cout << ustd::count_if(v.begin(), v.end(), &[](int i)-> bool //вывод 3
{
return i > 2;
});
return 0;
}
UPD 2 : Похоже зависит от версии компилятора. Для верхнего примера C++14 (gcc 8.3) кидает справедливую ошибку:
error: taking address of temporary [-fpermissive]
std::cout << ustd::count_if(v.begin(), v.end(), &Functor(2));
Ответы (2 шт):
Потому что через указатель вы не сможете передать, например, лямбда-функцию.
Через ссылку - можно, но только константную. Но тогда вы не сможете передать ту же лямбду, которая, например, захватывает что-то по ссылке...
Попробуйте сами - убедитесь, что возможности передачи чего угодно существенно уменьшаются...
Тут - https://ideone.com/11ctGT - я выложил код с передачей через указатель. И две закомментированные лямбды. Поиграйтесь, раскомментируйте и добейтесь, чтоб код скомпилировался...
template<class InputIt, class UnaryPredicate>
typename iterator_traits<InputIt>::difference_type
Count_if(InputIt first, InputIt last, UnaryPredicate * p)
{
typename iterator_traits<InputIt>::difference_type ret = 0;
for (; first != last; ++first) {
if ((*p)(*first)) {
ret++;
}
}
return ret;
}
bool even(int i)
{
return i%2 == 0;
}
int main(int argc, const char * argv[])
{
vector<int> v = { 1,2,3,4,5,6,7,8,9,0};
int total = 0;
//cout << Count_if(v.begin(),v.end(),[](int x) { return x%2 == 0; }) << endl;
//cout << Count_if(v.begin(),v.end(),[&total](int x) { ++total; return x%2 == 0; }) << endl;
cout << Count_if(v.begin(),v.end(),even) << endl;
}
Update
Кстати, Мейерс (Эффективный и современный С++, раздел 5.5, стр. 190) прямо советует -
Подход, который часто позволяет добиться производительности без увеличения сложности, заключается в замене передачи параметров по ссылке передачей по значению.
Кстати, логично. Функция и так будет передаваться через указатель, а лямбда или создаваемый функтор только усугубят ситуацию разыменованием, которое не нужно...
Видимо вас вводит в заблуждение запись аргумента как UnaryPredicate p. Она не означает передачу по значению. Если тип UnaryPredicate будет Functor const &, то будет передаваться ссылка. Пример (online compiler):
#include <algorithm>
#include <vector>
#include <cassert>
// non copyable, non movable
class
t_Predicate
{
public: t_Predicate(void) {}
private: t_Predicate(t_Predicate const & other) = delete;
private: t_Predicate(t_Predicate && other) = delete;
private: void operator =(t_Predicate const & other) = delete;
private: void operator =(t_Predicate & other) = delete;
public: bool operator ()(int const & value) const { return 0 != (value % 2); }
};
int main()
{
::std::vector const values{0, 2, 3, 7, 9, 12};
t_Predicate const pred{};
auto const count
{
::std::count_if
<
decltype(values)::const_iterator
, t_Predicate const &
>
(
values.begin(), values.end(), pred
)
};
assert(2 == count);
return 0;
}
Работает в msvc, но не работает в gcc. Почему так? Потому что в ненормативной секции стандарта реализациям дается разрешение на копирование предикатов, что и пытается делать библиотека, идущая с gcc. Мне лень искать тикет, но вроде это известный недостаток. Ну а пока для совместимости используйте reference_wrapper или его аналоги, как то рекомендуется в стандарте:
28.3 Algorithms requirements [algorithms.requirements]
8 [Note: Unless otherwise specified, algorithms that take function objects as arguments are permitted to copy those function objects freely. Programmers for whom object identity is important should consider using a wrapper class that points to a noncopied implementation object such as reference_wrapper (23.14.5), or some equivalent solution. —end note ]