gpt4 book ai didi

c++ - 如何在 std::function 的签名上重载构造函数?

转载 作者:IT老高 更新时间:2023-10-28 23:20:16 28 4
gpt4 key购买 nike

我正在尝试编写一个带有接受 std::function 对象作为参数的重载构造函数的类,但是当然 每件该死的事情都可以隐式转换为任何签名的 std::function .这自然很有帮助。

例子:

class Foo {
Foo( std::function<void()> fn ) {
...code...
}
Foo( std::function<int()> fn ) {
...code...
}
};

Foo a( []() -> void { return; } ); // Calls first constructor
Foo b( []() -> int { return 1; } ); // Calls second constructor

这不会编译,提示两个构造函数本质上相同且模棱两可。这当然是胡说八道。我已经尝试过 enable_if、is_same 和其他一些东西。接受函数指针是不可能的,因为这会阻止有状态 lambda 的传递。肯定有办法做到这一点吗?

恐怕我的模板技巧有点欠缺。普通的模板类和函数很容易,但是用编译器玩愚蠢的 bug 有点不合我意。有人可以帮帮我吗?

我知道以前有人问过这个问题的变体,但它们通常侧重于普通函数而不是构造函数;或通过参数重载而不是返回类型。

最佳答案

这里有一些常见的情况,以及为什么我不认为 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与模板推导一起使用,如 this question 所示。和 that one .这往往会增加更多的痛苦和困难。


简单地说,std::function<Sig>在标准库中没有特别处理。它仍然是一个用户定义的类型(在某种意义上,它不像 int),它遵循正常的重载决议、转换和模板推导规则。这些规则非常复杂并且相互交互——它不是为界面用户提供的服务,他们必须牢记这些规则才能将可调用对象传递给它。 std::function<Sig>具有悲剧性的吸引力,它看起来有助于使界面简洁且更具可读性,但只要您不重载这样的界面,这确实是正确的。

我个人有很多可以根据签名检查类型是否可调用的特征。结合 expressive EnableIf or Requires clauses我仍然可以保持可接受的可读界面。反过来,结合一些 ranked overloads我大概可以实现“如果仿函数产生可转换为 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 精神中的特征检查给定的签名——也就是说,它们检查一些给定的参数和一些预期的返回类型。他们不进行内省(introspection),因此他们在面对例如重载的仿函数。

关于c++ - 如何在 std::function 的签名上重载构造函数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16492470/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com