GuyGizmo GuyGizmo - 19 days ago 5
C++ Question

C++: how can I overload a function so that it can take any functor, including pointer to member functions?

In order to broaden my understanding of C++11, I'm experimenting with writing functional helpers and seeing if I can make calling them less verbose. Right now I'm trying to write a

some
function that returns true if any item in a collection passes a test. I want it to work with any collection type, be able to take any callable, and not require template arguments.

Essentially, I want it to work such that the following code will compile:

// insert declaration of some here
#include <list>
#include <functional>

class Foo {
public:
bool check() const { return true; };
};

class DerivedFooList : public std::list<Foo> {
public:
DerivedFooList(std::initializer_list<Foo> args) : std::list<Foo>(args) {};
};

class DerivedFooPtrList : public std::list<Foo *> {
public:
DerivedFooPtrList(std::initializer_list<Foo *> args) : std::list<Foo *>(args) {};
};

bool intCheck(int a) { return a == 1; }
bool fooCheck(const Foo &a) { return a.check(); }
bool fooPtrCheck(const Foo *a) { return a->check(); }

int main()
{
Foo a, b, c;
std::list<int> intList = {1, 2, 3};
std::list<Foo> fooList = {a, b, c};
std::list<Foo *> fooPtrList = {&a, &b, &c};
DerivedFooList derivedFooList = {a, b, c};
DerivedFooPtrList derivedFooPtrList = {&a, &b, &c};

auto localIntCheck = [] (int a) { return a == 1; };
auto localFooCheck = [] (const Foo &a) { return a.check(); };
auto localFooPtrCheck = [] (const Foo *a) { return a->check(); };

some(intList, [] (int a) { return a == 1; });
some(intList, &intCheck);
some(intList, localIntCheck);

some(fooList, [] (const Foo &a) { return a.check(); });
some(fooList, &fooCheck);
some(fooList, localFooCheck);
some(fooList, &Foo::check);

some(fooPtrList, [] (const Foo *a) { return a->check(); });
some(fooPtrList, &fooPtrCheck);
some(fooPtrList, localFooPtrCheck);
some(fooPtrList, &Foo::check);

some(derivedFooList, [] (const Foo &a) { return a.check(); });
some(derivedFooList, &fooCheck);
some(derivedFooList, localFooCheck);
some(derivedFooList, &Foo::check);

some(derivedFooPtrList, [] (const Foo *a) { return a->check(); });
some(derivedFooPtrList, &fooPtrCheck);
some(derivedFooPtrList, localFooPtrCheck);
some(derivedFooPtrList, &Foo::check);
return 0;
}


Note that if the value type of the collection is an object or object pointer, I want to be able to pass a pointer to a member function as the second argument to
some
. And that's where things get hairy.

My first attempt was to implement it like this:

template <class T, class F>
bool some(const T &list, F &&func)
{
for(auto item : list) {
if (func(item)) {
return true;
}
}
return false;
}

template <template<class, class> class T, class U, class V, class W>
bool some(const T<U, V> &list, bool (W::*func)() const)
{
return some(list, [=](U const &t){ return (t.*func)(); });
}

template <template<class, class> class T, class U, class V, class W>
bool some(const T<U *, V> &list, bool (W::*func)() const)
{
return some(list, [=](U const *t){ return (t->*func)(); });
}


...but it doesn't work if the collection is not an STL collection, or at least one that takes two template arguments. In my example, using
DerivedFooList
or
DerivedFooPtrList
won't work.

My second attempt looked like this:

template <class T, class F>
bool some(const T &list, F &&func)
{
for(auto item : list) {
if (func(item)) {
return true;
}
}
return false;
}

template <class T, class U>
bool some(const T &list, bool (U::*func)() const)
{
return some(list, [=](U const &t) { return (t.*func)(); });
}


That works with
DerivedFooList
now, but doesn't work with
std::list<Foo *>
or
DerivedFooPtrList
.

My third attempt was this:

template <class T, class F>
bool some(const T &list, F &&func)
{
for(auto item : list) {
if (func(item)) {
return true;
}
}
return false;
}

template <class T, class U>
bool some(typename std::enable_if<std::is_class<typename T::value_type>::value, T>::type const &list, bool (U::*func)() const)
{
return some(list, [=](U const &t) { return (t.*func)(); });
}

template <class T, class U>
bool some(typename std::enable_if<std::is_pointer<typename T::value_type>::value, T>::type const &list, bool (U::*func)() const)
{
return some(list, [=](U const *t) { return (t->*func)(); });
}


Ignoring for the time being that this would only work with collections that have a member named
value_type
, it still doesn't allow my above example to compile. I think (but am not 100% sure) the reason is that it can't deduce T for the second two versions of
some
for the cases where I want it to be used.

I've tried very hard to see if there's a way to get what I want out of C++11, and I suspect that there is, but I can't figure it out. Is what I want possible, and if so, how do I do it?

Answer
template<class R, class F>
bool some( R const& r, F&& f ) {
  for(auto&& x:r)
    if (std::ref(f)(decltype(x)(x)))
      return true;
  return false;
}

std::ref overloads () to do the INVOKE concept, which in C++17 can be accessed directly via std::invoke. Your requirements seem to line up with the INVOKE concept.

decltype(x)(x) is equivalent to a std::forward expression if x is a deduced auto&& variable. Read it as "treat x as if it was the type it was declared as". Note that if x is an auto value, it copies, unlike forward.

INVOKE( pmf, ptr ) and INVOKE( pmf, ref ) both work. So no need to do fancy stuff in the overload.