我正在编写一个小事件管理器类,我将一些函数指针存储在向量中。我使用std::function<void(int)>
作为矢量类型,我测试了在其中插入lambdas和普通函数并且它可以工作:
void t(int p){
/*things*/
}
[...]
event.bind([](int p){/*things*/});
event.bind(t);
现在,(在某个时刻我需要删除lambda而不是函数,)我的问题是:
是否可以区分lambda和函数?如果是,怎么做?
修改
由于我澄清了我的怀疑,这个问题就变成了标题所说的
答案 0 :(得分:4)
真正的答案是:您不想这样做。如果您真的想知道原始类型,它也会失败类型擦除仿函数。这只是闻起来很糟糕的设计。
您可能正在寻找的是std::function::target_type
。这是一种拉出type_info
对象存储的目标函数的基础function
的方法。每个type_info
都有一个name()
,可以进行解码。请注意,这是一个非常深的兔子洞,你基本上必须硬编码各种奇怪的边缘情况。我一直在感谢Yakk非常热爱的帮助。
不同的编译器以不同的方式破坏他们的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
}
答案 1 :(得分:3)
不,不是一般的。
std::function<void(int)>
可以存储一个函数指针,指向可以通过传递单个右值int
来调用的任何函数。有无数个这样的签名。
lambda的类型是每个声明的唯一匿名类。两个不同的lambda不共享任何类型关系。
您可以确定std::function<void(int)>
存储特定类型的变量,但在函数指针和lambda情况下,存在可以存储在std::function
中的无限数量的不同类型考虑。并且您只能测试&#34;完全等于类型&#34;。
您可以访问类型ID信息,但那里没有可移植的表示,并且通常将该信息用于身份匹配(和相关)以外的任何事情或调试是一个坏主意。
现在,问题的限制版本(可以判断std::function<void(int)>
是否包含类型void(*)(int)
的函数指针)很容易解决。但总的来说,这样做仍然是一个坏主意:首先,因为它很精致(代码远离你使用它的点,就像对函数签名的微妙改变,可以破坏事物),其次,检查和改变你的行为基于存储在std::function
中的类型,只应在极端情况下进行(通常涉及将代码从使用void*
样式回调更新为std::function
样式回调)。
答案 2 :(得分:2)
注意:此答案预先假定存在可分配为事件处理程序的有限且不同数量的函数签名。它假定使用错误的签名分配任何旧函数是错误的。
您可以使用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';
}
答案 3 :(得分:2)
无论是函数指针还是lambda,它都以std::function<void(int)>
中的vector
结尾。然后std::function<void(int)>
负责管理函数指针或lambda,而不是你的。也就是说,您只需从std::function<void(int)>
中删除所需的vector
即可。 std::function<void(int)>
的析构函数知道如何正确地做事。在你的情况下,这将无法使用函数指针并调用lambdas的析构函数。 std::function<void(int)>
使您能够以一种美好而统一的方式对待不同的事物。不要滥用它。