基本上,我想要实现的是编译时验证(可能会有漂亮的错误消息)注册的可调用对象(函数、lambda、带调用操作符的结构体)是否有正确的签名。示例(static_assert
的内容将被填充):
struct A {
using Signature = void(int, double);
template <typename Callable>
void Register(Callable &&callable) {
static_assert(/* ... */);
callback = callable;
}
std::function<Signature> callback;
};
7条答案
按热度按时间jum4pzuy1#
您可以使用std::is_convertible(C++11起),例如:
或
LIVE
ctzwtxfj2#
C++20(含概念)
几年前,我们就有了一些概念,可以让这样的检查变得容易。这个例子是
std::invocable
的直接使用。C++17语言
在C17中有trait
std::is_invocable<Callable, Args...>
,它能完全满足你的要求,它比std::is_convertible<std::function<Signature>,...>
的优点是你不必指定返回类型。这听起来可能有点过头了,但我遇到了不得不使用它的问题。确切地说,我的 Package 器函数从传递的Callable推导出了它的返回类型,但我传递了像
[](auto& x){return 2*x;}
这样的模板化lambda,所以它的返回类型是在subcall中推导出来的。我不能将它转换为std::function
,最后我使用了is_invocable
的C14本地实现。C++14的后端移植
我找不到原作者的信用。无论如何,代码:
以你为例:
qyswt5oh3#
大部分的答案都集中在基本回答这个问题上:你能用这些类型的值调用给定的函数对象吗?这和匹配签名不一样,因为它允许很多你说你不想要的隐式转换。为了得到更严格的匹配,我们必须做很多TMP。首先,这个答案:带部分可变参数的Call函数演示了如何获取参数的确切类型和可调用对象的返回类型。代码如下所示:
完成这些操作后,现在可以在代码中放置一系列静态Assert:
因为你是通过值传递的,所以这基本上就是你所需要的。如果你是通过引用传递的,我会添加一个额外的静态Assert来使用其他的答案之一;可能是songyuoyao的答案。这将处理一些情况,例如基类型是相同的,但是const限定符的方向错误。
当然,你也可以让这些都通用于
Signature
类型,而不是像我一样(简单地重复静态Assert中的类型),这样做会更好,但是它会给一个已经很重要的答案增加更复杂的TMP;如果您觉得您将在许多不同的Signature
中使用它,或者它经常更改,那么可能也值得添加这些代码。下面是一个活生生的例子:http://coliru.stacked-crooked.com/a/cee084dce9e8dc09。特别是我的示例:
brccelvz4#
如果您接受在可变参数模板类中转换
A
,则仅当callable
兼容时,才可以使用decltype()
激活Register
,如下所示这样,如果您愿意,使用不兼容的函数调用
Register()
,您可以获得软错误并激活另一个Register()
函数y1aodyip5#
当你能使用C++17时,这是@R2RT答案的另一个版本。我们可以使用trait
is_invocable_r
来完成这项工作:输出
38.5
std::is_invocable_r
的优点在于,它允许您控制返回类型和参数类型沿着而std::is_invocable
仅用于可调用的参数类型。xt0899hw6#
你可以使用检测习语,它是sfinae的一种形式,我相信这在c++11中是有效的。
然后,您可以在代码中编写一个静态Assert,如下所示:
与我上面看到的答案相比,这种方法的优点是:
static_assert
,并在其中放置一个很好的人类可读的错误消息Tartan Llama写了一篇关于这种技术的很棒的博客,还有几种替代方法,看看吧!https://blog.tartanllama.xyz/detection-idiom/
如果你需要做很多这样的事情,那么你可能想看看callable_traits库。
jvlzgdj97#
基于@尼尔·弗里德曼的回答。
然后你可以做一些严格的检查