4

I have this function that returns true if any of the elements in the list match the given predicate

bool _any(bool (*predicate)(MyStructure), list<MyStructure> arr)
{
    for (int i = 0; i < arr.size(); i++)
    {
        if (predicate(arr.front()))
            return true;
        arr.pop_front();
    }
    return false;
}

This works for very simple lambdas, but if the given lambda needs to capture this then I have an error which I can't figure out how to fix.

Assert::IsTrue(_any(
    [this](MyStructure t)
    {
        return t._name == "NAME_SEARCHED" &&
            t._type == "TYPE_SEARCHED" &&
            _any([](OtherStruct o) { return o._name == "SEARCHED_2"; }, t._children);
    },
    myList));

Error:

cannot convert argument 1 from 'UTests::<lambda_e8bda0383e9f0c2ae44be631a7424852>' 
to 'bool (__cdecl *)(MyNameSpace::MyStructure)'

(N.B. : _any that takes an OtherStruct is defined as well).

JeJo
  • 30,635
  • 6
  • 49
  • 88
Arthur Attout
  • 2,701
  • 2
  • 26
  • 49
  • 3
    The easiest ways are with `std::function` or templates. – sweenish Aug 24 '21 at 12:11
  • 1
    @JeJo indeed, I fixed that since then. I just dived into c++ literally today (I have a strong C# background) and this is mostly copy/paste stuff that I'll review / enhance later. The point was just to ensure the basics work. – Arthur Attout Aug 24 '21 at 19:45

2 Answers2

5

You can not convert the stateful lambda to function pointer!

The problem is, that _any function expects a typed function pointer of type bool (*)(MyStructure) not a lambda function. You could have converted the lambda to a function pointer, if it would have been a capture-less lambda.

That means, here

Assert::IsTrue(_any(
[this](MyStructure t)
//^^^^ ---> capture the "this"
{
   // ...
},
myList));

you are trying to convert a lambda function(with capturing the instance) to a typed function pointer. This is simply not possible, hence, the compiler error.


I can't figure out how to fix.

Make the _any a template function, so that the compiler can do the deduction for you.

template<typename Callable>
bool _any(Callable predicate, std::list<MyStructure> const& arr)
{
   // ...
    return false;
}

or using std::function, with some type erasure overhead

#include <functional> // std::function

bool _any(std::function<bool(MyStructure)> const& predicate
    , std::list<MyStructure> const& arr)
{
   // ...
    return false;
}
JeJo
  • 30,635
  • 6
  • 49
  • 88
  • I actually went for `std::function predicate` which I guess is the same, but I'd trust that this is a working solution aswell. Any difference between the two ? – Arthur Attout Aug 24 '21 at 12:19
  • Worth noting that if you don't mind a few layers of abstractions, you can do it without templates or `std::function`. – sweenish Aug 24 '21 at 12:33
0

Just use a template argument as in std::for_each().

By the way, I'm not certain passing a list by value and consuming it is necessary.

template<typename Predicate>
bool _any(Predicate predicate, const list<MyStructure> &arr)
{
    for(const auto &elem: arr)
    {
        if (predicate(elem))
            return true;
    }
    return false;
}

Finally, you could rely on std::any_of().

template<typename Predicate>
bool _any(Predicate predicate, const list<MyStructure> &arr)
{
    return std::any_of(cbegin(arr), cend(arr), predicate);
}

Look at the existing functionalities of <algorithm>, almost « everything » already exists and can be adapted to specific contexts thanks to the lambda-closures expected as template arguments.

In general, std::function should only be used when the closure needs to be stored in order to be called later (thread, callback...). If the closure is directly used in the function, and no longer needs to exist when the function returns, then the template solution is preferred because it saves a bit of memory and efficiency.

prog-fh
  • 13,492
  • 1
  • 15
  • 30