Sod*_*hty 12 c++ lambda constructor overloading c++11
我正在尝试编写一个带有重载构造函数的类,它接受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
Run Code Online (Sandbox Code Playgroud)
这将无法编译,抱怨两个构造函数基本相同且含糊不清.当然,这是无稽之谈.我尝试过enable_if,is_same和其他一些东西.接受函数指针是不可能的,因为这会阻止有状态lambda的传递.当然必须有办法实现这一目标吗?
我害怕,我的模板技能有点缺乏.普通的模板类和函数很容易,但使用编译器玩傻傻的玩家有点超出了我的联盟.有人可以帮帮我吗?
我之前已经知道这个问题的变体,但它们通常关注的是正常的函数而不是构造函数; 或者通过参数而不是返回类型进行重载.
以下是一些常见的场景,以及为什么我认为std::function不适合它们:
struct event_queue {
using event = std::function<void()>;
std::vector<event> events;
void add(event e)
{ events.emplace_back(std::move(e)); }
};
Run Code Online (Sandbox Code Playgroud)
在这种直截了当的情况下,存储特定签名的函子.在那种情况下,我的建议似乎很糟糕,不是吗?怎么可能出错?喜欢的东西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)); }
};
Run Code Online (Sandbox Code Playgroud)
问题在于,简单的事情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; });
Run Code Online (Sandbox Code Playgroud)
也有充分的理由:std::function<void()> f = [] { return 4; };不是错误,而且运行正常.返回值被忽略并被遗忘.
有时std::function与模板演绎一起使用,如本问题和那个问题所示.这往往会增加一层痛苦和艰辛.
简单地说,std::function<Sig>在标准库中没有特别处理.它仍然是用户定义的类型(在某种意义上,它不同于它int)遵循正常的重载分辨率,转换和模板推导规则.这些规则非常复杂并且彼此交互 - 它不是对接口用户的服务,他们必须记住这些以将可调用对象传递给它.std::function<Sig>它具有那种悲剧性的诱惑,它看起来有助于使界面简洁,更具可读性,但只要你不重载这样的界面,这才真正成立.
我个人有一大堆特征可以检查一个类型是否可以根据签名调用.结合表达EnableIf或Requires条款,我仍然可以保持可接受的可读界面.反过来,结合一些排名的重载,我可以实现这样的逻辑:"如果functor 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>);
};
Run Code Online (Sandbox Code Playgroud)
请注意,特征是is_callable检查给定签名的精神- 也就是说,它们检查某些给定的参数和一些预期的返回类型.它们不执行内省,因此它们在例如重载函子的情况下表现良好.
因此有很多方法可以解决这个问题,这需要不同的工作量。它们都不是完全微不足道的。
T::operator()首先,您可以通过检查和/或检查类型是否为 来解压传入类型的签名R (*)(Args...)。
然后检查签名等效性。
第二种方法是检测呼叫兼容性。编写一个如下的特征类:
template<typename Signature, typename F>
struct call_compatible;
Run Code Online (Sandbox Code Playgroud)
这是std::true_type或std::false_type取决于是否decltype<F&>()( declval<Args>()... )可转换为Signature返回值。在这种情况下,这将解决您的问题。
现在,如果您重载的两个签名兼容,则需要完成更多工作。即,想象一下,如果您有一个std::function<void(double)>并且std::function<void(int)>- 它们是交叉调用兼容的。
要确定哪个是“最佳”签名,您可以查看我之前的一个问题,我们可以在其中获取一堆签名并找到最匹配的签名。然后进行返回类型检查。这变得越来越复杂!
如果我们使用该call_compatible解决方案,您最终要做的是:
template<size_t>
struct unique_type { enum class type {}; };
template<bool b, size_t n=0>
using EnableIf = typename std::enable_if<b, typename unique_type<n>::type>::type;
class Foo {
template<typename Lambda, EnableIf<
call_compatible<void(), Lambda>::value
, 0
>...>
Foo( Lambda&& f ) {
std::function<void()> fn = f;
// code
}
template<typename Lambda, EnableIf<
call_compatible<int(), Lambda>::value
, 1
>...>
Foo( Lambda&& f ) {
std::function<int()> fn = f;
// code
}
};
Run Code Online (Sandbox Code Playgroud)
这是其他解决方案的一般模式。
这是第一次尝试call_compatible:
template<typename Sig, typename F, typename=void>
struct call_compatible:std::false_type {};
template<typename R, typename...Args, typename F>
struct call_compatible<R(Args...), F, typename std::enable_if<
std::is_convertible<
decltype(
std::declval<F&>()( std::declval<Args>()... )
)
, R
>::value
>::type>:std::true_type {};
Run Code Online (Sandbox Code Playgroud)
尚未测试/未编译。
| 归档时间: |
|
| 查看次数: |
644 次 |
| 最近记录: |