9

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);
}
user7860670
  • 35,849
  • 4
  • 58
  • 84
  • There's a proposal for [](auto&& t) -> t.IsLucky(), which is as condensed as it's likely to get. Note that it's also more general: -> t.sLucky(1) || t.isLucky(2) works as well. Maybe one day we'll have '@' representing auto&& for variables (and maybe using for types). – lorro Nov 11 '17 at 12:39
  • 2
    FWIW, there's also [`std::mem_fn`](http://en.cppreference.com/w/cpp/utility/functional/mem_fn). – Tony Delroy Nov 11 '17 at 12:45
  • `bool Is_Lucky(void) const {return(8 == m_number);}` - `return` is not a function. Those parentheses are not needed. `return 8 == m_number;` is (IMHO) more idiomatic. Also, that `void` to indicate no function arguments is a C-ism - don't do that in C++ - just `bool Is_Lucky() const` will do. – Jesper Juhl Nov 11 '17 at 13:59
  • @lorro What `[](auto&& t) -> t.IsLucky()` is supposed to do? Is it a lambda with `{return();}` thrown away? – user7860670 Nov 11 '17 at 14:02
  • @VTT it's proposed to mean the same as: [](auto&& t) -> decltype(t.isLucky()) { return t.isLucky(); } – lorro Nov 11 '17 at 14:06

1 Answers1

7

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

Vittorio Romeo
  • 90,666
  • 33
  • 258
  • 416
  • 1
    Why are additional overloads needed for member functions? It's a template already, why change? – Rakete1111 Nov 11 '17 at 12:46
  • @Rakete1111: you would have to pass the instance on which to invoke the member function as well. E.g. `std::remove_if(fst, last, &foo::some_predicate, my_foo_instance)`. – Vittorio Romeo Nov 11 '17 at 12:48
  • 1
    Wouldn't `&foo::some_predicate` be called on the values of the range, and not on another instance? – Rakete1111 Nov 11 '17 at 12:51
  • (2) My question implies that non-static member function used as predicate differs from `bool (Type & item)` function in that an item reference is passed as an implicit parameter. (3) more like marking `std::invoke` as `constexpr` would be required to mark algorithms with `constexpr` – user7860670 Nov 11 '17 at 12:56
  • @VTT: `std::invoke` cannot be marked as `constexpr` due to a CWG issue. Check the link in my answer. – Vittorio Romeo Nov 11 '17 at 12:57
  • But the first sentence says "there is possibility to implement standard conforming invoke function template as a constexpr function". And the counter arguments include difficulties with dealing with `mem_fn` that has been removed anyway. Though i'm not sure what CWG stands for. – user7860670 Nov 11 '17 at 13:01
  • @VTT: again, it's in the link I posted. [Here](http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1581). Until this CWG (core working group) issue is resolved, `std::invoke` cannot be marked as `constexpr`. – Vittorio Romeo Nov 11 '17 at 13:05