Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ distinguish Lambdas from Function pointers inside a vector

I'm writing a little event manager class where I store some function pointers inside a vector. I use std::function<void(int)> as vector type, I tested inserting inside it lambdas and normal functions and it works:

void t(int p){
  /*things*/
}
[...]
event.bind([](int p){/*things*/});
event.bind(t);

Now, (at a certain point I need to delete lambdas but not functions,) my question is:

Is it possible to distinguish lambdas from functions? If yes, how?

EDIT:
Since I clarified my doubts, this question becomes just what the title says

like image 590
Xriuk Avatar asked Dec 18 '22 23:12

Xriuk


1 Answers

The real answer is: you don't want to do this. It defeats the point of type-erasing functors if you actually want to know the original type also in case of whatever. This just smells like bad design.


What you are potentially looking for is std::function::target_type. This is a way to pull out the underlying type_info of the target function that the function object is storing. Each type_info has a name(), which can be demangled. Note that this is a very deep rabbit hole and you're basically going to have to hard-code all sorts of weird edge-cases. As I've been doing thanks to Yakk's very loving help.

Different compilers mangle their lambda names differently, so this approach doesn't even resemble portability. Quick checking shows that clang throws in a $ while gcc throws {lambda...#d}, So we can attempt to take advantage of that by writing something like:

bool is_identifier(std::string const& id) {
    return id == "(anonymous namespace)" ||
        (std::all_of(id.begin(), id.end(),
        [](char c){
            return isdigit(c) || isalpha(c) || c == '_';
        }) && !isdigit(id[0]));
}

bool is_lambda(const std::type_info& info)
{
    std::unique_ptr<char, decltype(&std::free)> own {
        abi::__cxa_demangle(info.name(), nullptr, nullptr, nullptr),
        std::free
    };

    std::string name = own ? own.get() : info.name();

    // drop leading namespaces... if they are valid namespace names
    std::size_t idx;
    while ((idx = name.find("::")) != std::string::npos) {
        if (!is_identifier(name.substr(0, idx))) {
            return false;
        }
        else {
            name = name.substr(idx+2);
        }
    }

#if defined(__clang__)
    return name[0] == '$';
#elif defined(__GNUC__)
    return name.find("{lambda") == 0;
#else
    // I dunno?
    return false;
#endif
}

And then throw that in your standard erase-remove idiom:

void foo(int ) { }
void bar(int ) { }
long quux(long x) { return x; }

int main()
{
    std::vector<std::function<void(int)>> v;

    v.push_back(foo);
    v.push_back(bar);
    v.push_back(quux);
    v.push_back([](int i) { std::cout << i << '\n';});

    std::cout << v.size() << std::endl; // prints 4

    v.erase(
        std::remove_if(
            v.begin(),
            v.end(),
            [](std::function<void(int)> const& f){
                return is_lambda(f.target_type());
            }),
        v.end()
        );

    std::cout << v.size() << std::endl; // prints 3
}
like image 61
Barry Avatar answered Feb 01 '23 07:02

Barry