Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ removing an std::function from a std::vector<std::function<...>>

Tags:

c++

c++11

I have the following system of callbacks

class ... {

    ...

    std::vector<std::function<void()>>        systemBringUps;
    std::vector<std::function<void()>>        systemTearDowns;
    std::vector<std::function<void(EntityID_t)>> systemMains;
    std::vector<std::function<bool(EntityID_t)>> systemChecks;

    template <typename T>
    void registerSystem() {
        systemBringUps.push_back(T::systemBringUp);
        systemTearDowns.push_back(T::systemTearDown);
        systemMains.push_back(T::systemMain);
        systemChecks.push_back(T::systemCheck);
        T::onSystemRegister();
    }

    template <typename T>
    void deregisterSystem() {
        std::function<void()> bringUp = T::systemBringUp;
        std::function<void()> tearDown = T::systemTearDown;
        std::function<void(EntityID_t)> smain = T::systemMain;
        std::function<bool(EntityID_t)> check = T::systemCheck;

        std::remove(systemBringUps.begin(), systemBringUps.end(), bringUp);
        std::remove(systemTearDowns.begin(), systemTearDowns.end(), tearDown);
        std::remove(systemMains.begin(), systemMains.end(), smain);
        std::remove(systemChecks.begin(), systemChecks.end(), check);
        T::onSystemDeregister();
    }

The registerSystem template function works fine, but the deregisterSystem function fails to compile, stating

...

/usr/bin/../lib/gcc/x86_64-linux-gnu/7.5.0/../../../../include/c++/7.5.0/bits/predefined_ops.h:241:17: error: invalid operands to binary expression
      ('std::function<bool (unsigned short)>' and 'const std::function<bool (unsigned short)>')
        { return *__it == _M_value; }
                 ~~~~~ ^  ~~~~~~~~
...

So it seems that std::remove cannot compile since the std::functions in deregisterSystem that I am defining are const, whereas those in the vectors I am trying to remove from are not? Is there any way I can discard the const qualifier from these std::functions? I have tried using the copy constructor as in

template <typename T>
    void deregisterSystem() {
        std::function<void()> bringUp(T::systemBringUp);
        std::function<void()> tearDown(T::systemTearDown);
        std::function<void(EntityID_t)> smain(T::systemMain);
        std::function<bool(EntityID_t)> check(T::systemCheck);

        std::remove(systemBringUps.begin(), systemBringUps.end(), bringUp);
        std::remove(systemTearDowns.begin(), systemTearDowns.end(), tearDown);
        std::remove(systemMains.begin(), systemMains.end(), smain);
        std::remove(systemChecks.begin(), systemChecks.end(), check);
        T::onSystemDeregister();
    }

but this fails in a similar way.

like image 671
riley lyman Avatar asked May 20 '20 22:05

riley lyman


2 Answers

Your problem is not constness but that that std::function objects can not be compared for equality at all. Your current approach simply will not work.

The solution is to give some sort of unique 'token' during registration to the caller which can be used to unregister later.

like image 175
orlp Avatar answered Oct 02 '22 23:10

orlp


You can use std::function<F>::target to get the held function object directly and compare that.

For example, to erase all of the functions that are T::systemBringUp in systemBringUps, you could do something like:

systemBringUps.erase(std::remove_if(systemBringUps.begin(), systemBringUps.end(), [](const auto& f) {
    auto* target = f.template target<decltype(T::systemBringUp)>();
    return target != nullptr && *target == T::systemBringUp;
}), systemBringUps.end());

You can make a helper function to check if a std::function is holding a value:

template<class F, class T>
bool is_function_holding(const std::function<F>& func, const T& target) {
    static_assert(std::is_assignable<std::function<F>&, const T&>::value, "Function could not possibly hold target");
    auto* current_target = func.template target<typename std::decay<T>::type>();
    return current_target != nullptr && *current_target == target;
}

// Later
systemBringUps.erase(std::remove_if(systemBringUps.begin(), systemBringUps.end(), [](const auto& f) {
    return is_function_holding(f, T::systemBringUp);
}, systemBringUps.end());
systemTearDowns.erase(std::remove_if(systemTearDowns.begin(), systemTearDowns.end(), [](const auto& f) {
    return is_function_holding(f, T::systemTearDown);
}, systemTearDowns.end());
// ...
like image 28
Artyer Avatar answered Oct 02 '22 23:10

Artyer