c++将Lambdas与vector中的Function指针区分开来

C++ distinguish Lambdas from Function pointers inside a vector

本文关键字:Function 指针 区分开 中的 vector Lambdas c++      更新时间:2023-10-16

我正在编写一个小事件管理器类,我在向量中存储一些函数指针。我使用std::function<void(int)>作为向量类型,我测试了在它内部插入lambda和正常函数,它工作:

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

现在,(在某一点上我需要删除lambdas而不是函数,)我的问题是:

是否有可能从函数中区分lambdas ?如果是,怎么做?

编辑:


既然我澄清了我的疑问,这个问题就变成了标题所说的

真正的答案是:你不想这样做。如果你想知道在任何情况下的原始类型,它就违背了类型消除函子的意义。这就像糟糕的设计。


你可能要找的是std::function::target_type。这是一种提取function对象存储的目标函数的底层type_info的方法。每个type_info都有一个name(),它可以被拆卸。请注意,这是一个非常深的兔子洞,你基本上必须硬编码各种奇怪的边缘情况。就像我一直在做的一样,感谢牦牛的热心帮助。

不同的编译器会以不同的方式处理它们的lambda名称,所以这种方法甚至不像可移植性。快速检查显示clang抛出一个$,而gcc抛出一个{lambda...#d},所以我们可以尝试通过这样写来利用这一点:

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
}

然后把它放到标准的擦除习惯用法中:

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
}

不,一般不。

std::function<void(int)>可以存储一个函数指针,该指针指向任何可以通过传递一个右值int调用的函数。有无数个这样的签名。

lambda的类型是每个声明的唯一匿名类。两个不同的lambda不共享任何类型关系。

您可以确定std::function<void(int)>是否存储特定类型的变量,但是在函数指针和lambda的情况下,可以考虑在std::function中存储的不同类型的数量是无限的。你只能测试"完全等于一个类型"。

您可以访问类型id信息,但那里没有可移植的表示,并且通常将该信息用于标识匹配(及相关)或调试以外的任何事情都是坏主意。

现在,这个问题的限制版本(你能告诉std::function<void(int)>是否包含void(*)(int)类型的函数指针)很容易解决。但总的来说,这样做仍然是一个坏主意:首先,因为它很微妙(代码远离你使用它的地方,就像对函数签名的细微改变,可能会破坏东西),其次,根据存储在std::function中的类型检查和改变你的行为应该只在极端的极端情况下完成(通常涉及更新你的代码从使用void*风格回调到std::function风格回调)。

无论是函数指针还是lambda,它最终作为vector中的std::function<void(int)>。然后std::function<void(int)>负责管理函数指针或lambda,而不是您的。这意味着,您只需从vector中删除您想要的std::function<void(int)>std::function<void(int)>的析构函数知道如何做正确的事情。在您的情况下,这将对函数指针不做任何事情,并调用lambda的析构函数。std::function<void(int)>使您能够以一种友好和统一的方式对待不同的事物。别误用了

注意:这个答案的前提是存在一个有限的、不同数量的函数签名可以被分配为事件处理程序。它假定用错误的签名赋值任何旧函数都是错误的。

您可以使用std::function::target确定哪些是函数指针,并通过消去过程确定哪些必须是lambdas:

void func1(int) {}
void func2(double) {}
int main()
{
    std::vector<std::function<void(int)>> events;
    events.push_back(func1);
    events.push_back([](int){});
    events.push_back(func2);
    for(auto& e: events)
    {
        if(e.target<void(*)(int)>())
            std::cout << "funcion int" << 'n';
        else if(e.target<void(*)(double)>())
            std::cout << "funcion double" << 'n';
        else
            std::cout << "must be lambda" << 'n';
    }
}

这可以工作,因为如果形参类型不匹配,std::function::target返回空指针

单变量示例:

void func(int) {}
int main()
{
    std::function<void(int)> f = func;
    if(f.target<void(*)(int)>())
        std::cout << "not a lambda" << 'n';
}