Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can pointers to non-static member functions not be used as a unary predicate for standard library algorithms?

Many algorithms from the standard library accept a unary predicate with a signature of bool (Type & item) so supplying a pointer to a non-static member function directly is not working. This seems to be rather restrictive considering that it seems to be possible to lift such a restriction by replacing direct invocation of operator () on the predicate with a call to std::invoke. Maybe the proposed approach has some drawbacks that I've overlooked?

Note: the non-static member function referred to in this question is supposed to differ from a regular function predicate only in that an item reference is passed as an implicit parameter rather than an explicit parameter.

Example code (online compiler):

#include <array>
#include <algorithm>
#include <iostream>
#include <functional>
#include <cassert>

template<typename TForwardIterator, typename TPredicate> TForwardIterator
my_find_if
(
    const TForwardIterator p_items_begin
,   const TForwardIterator p_items_end
,   TPredicate &&          predicate
)
{
    TForwardIterator p_item(p_items_begin);
//  while((p_items_end != p_item) && (!predicate(*p_item)))
    while((p_items_end != p_item) && (!::std::invoke(predicate, *p_item)))
    {
        ++p_item;
    }
    return(p_item);
}

class t_Ticket
{
    private: int m_number;

    public:
    t_Ticket(const int number): m_number(number) {}

    public: bool
    Is_Lucky(void) const {return(8 == m_number);}
};

int main()
{
    ::std::array<t_Ticket, 10> tickets{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    //  using standard library
    auto p_ticket1(::std::find_if(tickets.begin(), tickets.end(), [](const t_Ticket & ticket) {return(ticket.Is_Lucky());}));
    //  still works
    auto p_ticket2(my_find_if(tickets.begin(), tickets.end(), [](const t_Ticket & ticket) {return(ticket.Is_Lucky());}));
    //  same thing, but shorter and not sufferring from potential lambda code duplication
    auto p_ticket3(my_find_if(tickets.begin(), tickets.end(), &t_Ticket::Is_Lucky));
    //  using standard library like this won't compile
    //auto p_ticket4(::std::find_if(tickets.begin(), tickets.end(), &t_Ticket::Is_Lucky));

    assert(p_ticket1 == p_ticket2);
    assert(p_ticket2 == p_ticket3);
    return(0);
}
like image 574
user7860670 Avatar asked Nov 11 '17 12:11

user7860670


1 Answers

Firstly, assuming that the question is:

Why can't algorithms take a generic Callable as their predicate/action?

I can think of multiple reasons:

  • The Callable concept was introduced in C++11 - pre-C++11 algorithms were not designed with it in mind.

  • Accepting any Callable would require expanding concepts such as Predicate and allowing algorithms to conditionally take an additional argument for pointers to member functions. This might lead to unnecessary complexity that can be avoided by simply restricting the interface to FunctionObject and forcing the user to do the "binding".

  • This would increase the amount of overloads significantly, considering that there also are execution policy overloads now. Alternatively, it could make every algorithm a variadic template where the parameter pack can contain either zero or one arguments (in case *this needs to be provided for a Callable).

  • std::invoke is not constexpr. Using it might prevent algorithms to be marked as constexpr in the future.


If you are referring to the particular case where:

  • The algorithm operates over a homogeneous range of T.

  • The predicate is executed on every element of type T of the range.

  • T has a member function that could be used as a predicate.

Then, I can still think of some possible reasons:

  • std::invoke is still not constexpr. We could avoid using std::invoke for .*, but then we would need to provide two separate implementations per algorithm.

  • If the member function is a template or an overload set, then this would fail to compile and confuse beginners. Generic lambdas do not have this issue.

  • It would complicate the requirements for algorithms - there would need to be some sort of restriction on the type/signature of the member function that makes sure it is usable on the range of T.

Or maybe... it just hasn't been proposed yet. If you still think it's a valuable thing to have after the reasons I came up with, you can start here in order to learn how to write a proposal: https://isocpp.org/std/submit-a-proposal

like image 193
Vittorio Romeo Avatar answered Sep 21 '22 17:09

Vittorio Romeo