Почему в стандартном вызове 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 шт):

Автор решения: Harry

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

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

Попробуйте сами - убедитесь, что возможности передачи чего угодно существенно уменьшаются...

Тут - 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) прямо советует -

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

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

→ Ссылка
Автор решения: user7860670

Видимо вас вводит в заблуждение запись аргумента как 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 ]

→ Ссылка