6

Problem: The following code would be very expressive and concise, if not necessarily fast, were it not for the fact that it does not compile.

It does not compile because you cannot compare std::function instances with operator==(). And std::find() tries to do exactly that.

Of course, I could go for an entirely different kind of implementation, but stubborn as I am and fond as I am with the code below, I am looking for something "closest as possible" which works.

Who can provide me a pretty rewrite of the code below which does the same thing?

#include <functional>
#include <vector>

typedef std::function<bool(int)> Tester_t;
typedef std::vector<Tester_t> TesterSet_t;

bool Test(TesterSet_t &candidates, int foo)
{
    TesterSet_t dropouts;
    for( auto& tester : candidates )
    {
        if(!tester(foo))    
        {
            droputs.push_back(tester);
        }
    }

    while(!dropouts.empty())
    {
        // The following line is not compiling because std::function has no operator==()
        TesterSet_t::iterator culprit = 
            std::find( candidates.begin(), candidates.end(), dropouts.back() );
        candidates.erase(culprit);
        dropouts.pop_back();
    }
    return !candidates.empty();
}
BitTickler
  • 10,905
  • 5
  • 32
  • 53
  • Can you describe the underlying problem you are trying to solve? The reason `std::function` doesn't have a `==` operator is because it doesn't many any sense to compare functions that way. How can you possibly determine if two functions are equal? – Red Alert Jan 28 '15 at 23:27
  • Similar (possibly duplicate): http://stackoverflow.com/questions/5847072/comparing-stdfunction – M.M Jan 28 '15 at 23:28
  • Short answer: you should make a struct that contains a unique key and the `std::function`, and have a vector of those structs. It doesn't make much sense to actually use a `std::function` as a search key. [See here](http://stackoverflow.com/questions/4430425/stdvector-of-stdfunction) – M.M Jan 28 '15 at 23:29
  • I knocked up this code snippet so I won't have to bother anyone with the longer code I want to use this approach in. Basically you have set of choices - candidates as I call it here - and you confront it with some input and you want to keep only those candidates around who are still "in the game". As you say, std::function makes no sense ;) – BitTickler Jan 28 '15 at 23:29
  • @MattMcNabb This is how I will do it if no one here has a better idea. – BitTickler Jan 28 '15 at 23:31

4 Answers4

10

As others have said, you don't need comparison of std::functions for this. Using standard C++ facilities this can be efficiently (with linear complexity) implemented in two lines:

bool Test(TesterSet_t &candidates, int foo)
{
    candidates.erase(std::remove_if(candidates.begin(), candidates.end(),
        [foo](Tester_t& f){ return !f(foo); }), candidates.end());
    return !candidates.empty();
}
Anton Savin
  • 40,838
  • 8
  • 54
  • 90
2

You don't need equality here. Just erase as you go

for (auto it = candidates.begin(); it != candidates.end(); ) {
    if (! (*it)(foo) ) {
        it = candidates.erase(it);
    }
    else {
        ++it;
    }
}
return !candidates.empty();

This will be also be faster than the version in the question even if operator== was defined for std::function.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • 1
    Erasing while iterating is a good path to disaster. At least it was so for many years. But maybe C++11 voodoo makes this legal now? – BitTickler Jan 28 '15 at 23:48
  • 4
    Erasing while iterating has always been fine as long as you do it properly. – Retired Ninja Jan 28 '15 at 23:55
  • 1
    @user2225104 Lots of things lead to disaster if you do them wrong. Just learn the right way to do them. – Barry Jan 29 '15 at 01:58
  • In general, I'd say undesirable to alter containers while iterating.http://en.wikipedia.org/wiki/Erase-remove_idiom – rparolin Jan 29 '15 at 04:06
  • Erasing while iterating is fine, it's one of the main reasons why `erase` returns an iterator! (The associative containers didn't in C++03, but that got [fixed](http://cplusplus.github.io/LWG/lwg-defects.html#130) for C++11). – Jonathan Wakely Jan 29 '15 at 11:35
  • @rparolin Except sometimes you can't use erase-remove, so it's important to learn how to do it the other way. See [this question](http://stackoverflow.com/q/28215743/2069064) which I just happened across. – Barry Jan 29 '15 at 13:32
0

If you ever dont need to erase candidates you can write:

bool Test(TesterSet_t &candidates, int foo)
{
    return std::any_of(candidates.begin(), candidates.end(), [&foo](Tester_t &tester) {
        return tester(foo);
    });
}

UPDATE

Okay, you need to remove candidates

bool Test(TesterSet_t &candidates, int foo)
{
    candidates.erase(
        std::remove_if(candidates.begin(), candidates.end(), [&foo](Tester_t &tester) {
            return !tester(foo);
        }),
        candidates.end()
    );
    return !candidates.empty();
}
yivo
  • 3,324
  • 1
  • 18
  • 23
  • The erasing is part of it. And also, (remaining) candidates need to get a go at it. Your code returns when it finds the first one returning false. The objective of the code actually is to thin out the set of remaining candidates. – BitTickler Jan 28 '15 at 23:47
0

The simple answer is not to use std::function<...> in this case but rather something like std::function<...> which does define an equality operators. The approach to define an equality operator for function<...> is to detect upon construction whether the actual function object actually contains an equality operator and, if so, make the object comparable. Otherwise, you'd either produce an error or you'd consider objects holding this particular function object type to be incomparable.

The immediate observation is, however, that most function objects are not comparable! For example, lambda functions are not comparable and std::bind() and std::mem_fn() also don't yield comparable function objects. Again, there can be a custom implementation for std::bind() and std::mem_fn(). There is no way to make lambda functions comparable unless the have an empty capture in which case they could be turned into function pointers and could be compared.

The implementation of equality-aware function objects is a bit too long to quickly type into a response. However, you can have a look at my implementation at github for equality-comparable bind() and mem_fn(). See this answer for an implementation of an equality comparable version of std::function<...>. It may be desirable to have lambda functions be comparable, too, if they have the same signature and all captured values are equality comparable.

All that said, if you can avoid the need, it is probably best avoided. However, I have come across some uses cases where a comparable std::function<...> would be rather handy despite its restrictions (i.e., not all function objects will be covered).

Community
  • 1
  • 1
Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
  • Indeed. This is why I think since a long time that the auto/lambda/std::function approach is fundamentally flawed. They should really have introduced a new intrinsic type and make function values and function references first class citizens. It is not really the c++ users problems how they solve the capture context implementation. With that in place, efforts like C-- might have been saved all together. – BitTickler Jan 28 '15 at 23:59
  • The problem isn't so much to dealing with the capture. The problem is that you can capture objects (or reference to objects) which are not equality comparable. You'll want to have lambda function for these, e.g., when there is no need to compare them. So you'll end up with incomparable function objects. How to deal with them (consider it an error/consider them non-equal) depends a bit on context and I actually prefer that choice not to be baked into the standard (i.e., not define equality in this case). However, if captured objects are comparable, equality for lambda functions would be nice. – Dietmar Kühl Jan 29 '15 at 00:09