![](/img/trans.png)
[英]C++11 lambda can be assigned to std::function with incorrect signature
[英]C++11 std::set lambda comparison function
我想用自定义比较功能创建一个std::set
。 我可以使用operator()
将其定义为一个类,但是我想享受定义使用lambda的功能,因此我决定在具有std::set
的类的构造函数的初始化列表中定义lambda函数。 std::set
会员。 但是我无法获得lambda的类型。 在继续之前,这里有一个例子:
class Foo
{
private:
std::set<int, /*???*/> numbers;
public:
Foo () : numbers ([](int x, int y)
{
return x < y;
})
{
}
};
搜索后,我发现了两种解决方案:一种是使用std::function
。 只需将设置的比较函数类型设置为std::function<bool (int, int)>
然后像我一样完全传递lambda即可。 第二种解决方案是编写一个make_set函数,如std::make_pair
。
解决方案1:
class Foo
{
private:
std::set<int, std::function<bool (int, int)> numbers;
public:
Foo () : numbers ([](int x, int y)
{
return x < y;
})
{
}
};
解决方案2:
template <class Key, class Compare>
std::set<Key, Compare> make_set (Compare compare)
{
return std::set<Key, Compare> (compare);
}
问题是,我是否有充分的理由选择一个解决方案而不是另一个解决方案? 我喜欢第一个,因为它利用了标准功能(make_set不是标准功能),但是我想知道:使用std::function
会使代码(可能)变慢吗? 我的意思是,这是否会降低编译器内联比较函数的机会,还是应该足够聪明,使其表现出完全相同的行为,就像它是lambda函数类型而不是std::function
(我知道,在这种情况下,它可以不是lambda类型,但您知道,我是在问)。
(我使用GCC,但是我想知道流行的编译器通常会做什么)
总结,在我获得了很多答案之后:
如果速度至关重要,最好的解决方案是使用带有operator()
仿函数的类。 编译器最容易优化和避免任何间接调用。
为了便于维护和使用C ++ 11功能更好的通用解决方案,请使用std::function
。 它仍然很快(比函子慢一点,但可以忽略不计),您可以使用任何函数std::function
,lambda,任何可调用对象。
还有一个使用函数指针的选项,但是如果没有速度问题,我认为std::function
更好(如果使用C ++ 11)。
有一个选项可以在其他地方定义lambda函数,但随后您就无法从作为lambda表达式的比较函数中获得任何收益,因为您还可以使用operator()
将其设为一个类,并且定义的位置不会成为set构造。无论如何。
还有更多想法,例如使用委托。 如果您想对所有解决方案进行更彻底的解释,请阅读答案:)
编译器不太可能能够内联std :: function调用,而任何支持lambda的编译器几乎都可以内联functor版本,包括该functor是不是被std::function
隐藏的lambda。
您可以使用decltype
获取lambda的比较器类型:
#include <set>
#include <iostream>
#include <iterator>
#include <algorithm>
int main()
{
auto comp = [](int x, int y){ return x < y; };
auto set = std::set<int,decltype(comp)>( comp );
set.insert(1);
set.insert(10);
set.insert(1); // Dupe!
set.insert(2);
std::copy( set.begin(), set.end(), std::ostream_iterator<int>(std::cout, "\n") );
}
哪些打印:
1
2
10
是的, std::function
向您的set
引入了几乎不可避免的间接访问。 从理论上讲,编译器始终可以弄清楚对set
的std::function
所有使用都涉及在始终完全相同的lambda的lambda上调用它,这既困难又极其脆弱。
易碎,因为在编译器可以证明对std::function
所有调用实际上都是对lambda的调用之前,它必须证明对std::set
任何访问都无法将std::function
为除lambda之外的任何值。 这意味着它必须跟踪所有可能的路径以到达所有编译单元中的std::set
,并证明它们中没有一个这样做。
在某些情况下这可能是可行的,但是即使编译器设法证明它,相对无害的更改也可能破坏它。
另一方面,具有无状态operator()
的仿函数很容易证明行为,而涉及到的优化是日常工作。
所以是的,在实践中,我怀疑std::function
可能会更慢。 另一方面, std::function
解决方案比make_set
维护起来更容易维护,并且交换程序员时间以提高程序性能是很容易实现的。
make_set
具有严重的缺点,即必须从对make_set
的调用中推断出任何此类set
的类型。 通常, set
存储持久状态,而不是您在堆栈上创建的持久状态,然后超出范围。
如果您创建了静态或全局无状态Lambda auto MyComp = [](A const&, A const&)->bool { ... }
,则可以使用std::set<A, decltype(MyComp)>
语法创建一个set
可以持久存在,但易于编译器优化(因为decltype(MyComp)
所有实例都是无状态函子)和内联。 我指出这一点,因为您将set
粘贴在struct
。 (或者您的编译器是否支持
struct Foo {
auto mySet = make_set<int>([](int l, int r){ return l<r; });
};
我会感到惊讶!)
最后,如果您担心性能,请考虑使用std::unordered_set
更快(以无法按顺序遍历内容以及必须编写/查找良好的哈希为代价),以及经过排序的std::vector
如果您有两阶段“全部插入”,然后“反复查询内容”,则std::vector
更好。 只需将其首先填充到vector
,然后sort
unique
erase
进行sort
,然后使用免费的equal_range
算法即可。
无状态的lambda(即没有捕获的lambda)可以衰减为函数指针,因此您的类型可以是:
std::set<int, bool (*)(int, int)> numbers;
否则我会去make_set
解决方案。 如果您不使用单行创建功能(因为它是非标准的),则不会编写太多代码!
根据我在探查器中玩耍的经验,性能和美观之间的最佳折衷方案是使用自定义委托实现,例如:
https://codereview.stackexchange.com/questions/14730/impossfully-fast-delegate-in-c11
由于std::function
通常太重了。 我无法评论您的具体情况,但我不知道。
如果确定要将该set
作为类成员,并在构造函数时初始化其比较器,则不可避免要至少有一个间接级别。 考虑一下编译器所知道的,您可以添加另一个构造函数:
Foo () : numbers ([](int x, int y)
{
return x < y;
})
{
}
Foo (char) : numbers ([](int x, int y)
{
return x > y;
})
{
}
一旦有了类型为Foo
的对象, set
的类型就不会包含有关构造函数初始化其比较器的信息,因此要调用正确的lambda,需要对运行时选择的lambda operator()
进行间接调用。
由于您使用的是不可捕获的lambda,因此可以使用函数指针类型bool (*)(int, int)
作为比较器类型,因为不可捕获的lambda具有适当的转换功能。 当然,这将涉及通过函数指针进行的间接访问。
差异很大程度上取决于编译器的优化。 如果它在std::function
优化了lambda,那么它们是等效的,否则,您将在前者中引入一种在后者中没有的间接寻址。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.