以下是一些常见场景,以及我认为不std::function
适合它们的原因:
struct event_queue {
using event = std::function<void()>;
std::vector<event> events;
void add(event e)
{ events.emplace_back(std::move(e)); }
};
在这种简单的情况下,特定签名的函子被存储。从这个角度来看,我的建议似乎很糟糕,不是吗?会出什么问题?诸如此类的东西运行queue.add([foo, &bar] { foo.(bar, baz); })
良好,类型擦除正是您想要的功能,因为可能会存储异构类型的函子,因此其成本不是问题。这实际上是一种可以说std::function<void()>
在签名中使用add
是可以接受的情况。但请继续阅读!
在未来的某个时刻,您意识到某些事件在被回调时可能会使用一些信息——因此您尝试:
struct event_queue {
using event = std::function<void()>;
// context_type is the useful information we can optionally
// provide to events
using rich_event = std::function<void(context_type)>;
std::vector<event> events;
std::vector<rich_event> rich_events;
void add(event e) { events.emplace_back(std::move(e)); }
void add(rich_event e) { rich_events.emplace_back(std::move(e)); }
};
这样做的问题是,queue.add([] {})
只有保证对 C++14 有效的简单事情——在 C++11 中,允许编译器拒绝代码。(最近足够多的 libstdc++ 和 libc++ 是在这方面已经遵循 C++14 的两个实现。)event_queue::event e = [] {}; queue.add(e);
但仍然有效!因此,只要您针对 C++14 进行编码,就可以使用它。
然而,即使使用 C++14,这个特性也std::function<Sig>
可能并不总是如你所愿。考虑以下内容,它现在是无效的,并且也将在 C++14 中:
void f(std::function<int()>);
void f(std::function<void()>);
// Boom
f([] { return 4; });
也有充分的理由:std::function<void()> f = [] { return 4; };
不是错误并且工作正常。返回值被忽略和遗忘。
有时std::function
与模板推导一起使用,如这个问题和那个问题中所见。这往往会增加更多的痛苦和困难。
简单地说,std::function<Sig>
在标准库中没有特别处理。它仍然是一个用户定义的类型(在某种意义上它与 eg 不同int
),它遵循正常的重载决议、转换和模板推导规则。这些规则非常复杂并且相互交互——它不是为界面用户提供的服务,他们必须牢记这些规则才能将可调用对象传递给它。std::function<Sig>
具有悲剧性的魅力,它看起来有助于使界面简洁且更具可读性,但只要您不重载这样的界面,这确实是正确的。
我个人有很多可以根据签名检查类型是否可调用的特征。结合表达性EnableIf
或Requires
子句,我仍然可以维护一个可接受的可读界面。反过来,结合一些排名重载,我大概可以实现“如果函子int
在没有参数的情况下产生可转换为的东西,则调用此重载,否则回退到此重载”的逻辑。这可能看起来像:
class Foo {
public:
// assuming is_callable<F, int()> is a subset of
// is_callable<F, void()>
template<typename Functor,
Requires<is_callable<Functor, void()>>...>
Foo(Functor f)
: Foo(std::move(f), select_overload {})
{}
private:
// assuming choice<0> is preferred over choice<1> by
// overload resolution
template<typename Functor,
EnableIf<is_callable<Functor, int()>>...>
Foo(Functor f, choice<0>);
template<typename Functor,
EnableIf<is_callable<Functor, void()>>...>
Foo(Functor f, choice<1>);
};
请注意,特征本着is_callable
检查给定签名的精神——也就是说,它们检查一些给定的参数和一些预期的返回类型。它们不执行自省,因此它们在面对例如重载的函子时表现良好。