Bri Bri
Bri Bri

Reputation: 2270

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?

Upvotes: 0

Views: 73

Answers (1)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275340

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.

Upvotes: 2

Related Questions