Reputation: 2270
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
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