基本上,我想要实现的是编译时验证(可能很好的错误消息)注册可调用(函数,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;
};
大多数答案都集中在基本上回答这个问题:你能用这些类型的值调用给定的函数对象吗?这与匹配签名不同,因为它允许您说您不想要的许多隐式转换。为了获得更严格的匹配,我们必须做一堆TMP。首先,这个答案:Call function with part of variadic arguments展示了如何获取参数的确切类型和返回可调用类型。代码转载于此:
template <typename T>
struct function_traits : public function_traits<decltype(&T::operator())>
{};
template <typename ClassType, typename ReturnType, typename... Args>
struct function_traits<ReturnType(ClassType::*)(Args...) const>
{
using result_type = ReturnType;
using arg_tuple = std::tuple<Args...>;
static constexpr auto arity = sizeof...(Args);
};
template <typename R, typename ... Args>
struct function_traits<R(&)(Args...)>
{
using result_type = R;
using arg_tuple = std::tuple<Args...>;
static constexpr auto arity = sizeof...(Args);
};
完成后,您现在可以在代码中放置一系列静态断言:
struct A {
using Signature = void(int, double);
template <typename Callable>
void Register(Callable &&callable) {
using ft = function_traits<Callable>;
static_assert(std::is_same<int,
std::decay_t<std::tuple_element_t<0, typename ft::arg_tuple>>>::value, "");
static_assert(std::is_same<double,
std::decay_t<std::tuple_element_t<1, typename ft::arg_tuple>>>::value, "");
static_assert(std::is_same<void,
std::decay_t<typename ft::result_type>>::value, "");
callback = callable;
}
std::function<Signature> callback;
};
由于您通过了价值,这基本上就是您所需要的。如果您通过引用传递,我会添加一个额外的静态断言,您可以使用其中一个其他答案;可能是宋元瑶的回答。这样可以处理例如基本类型相同但const限定方向错误的情况。
你当然可以在Signature
类型上使这一切都是通用的,而不是做我做的事情(简单地重复静态断言中的类型)。这会更好,但它会为已经非常重要的答案添加更复杂的TMP;如果你觉得你将使用它与许多不同的Signature
s或它经常更改它也可能值得添加该代码。
这是一个实例:http://coliru.stacked-crooked.com/a/cee084dce9e8dc09。特别是我的例子:
void foo(int, double) {}
void foo2(double, double) {}
int main()
{
A a;
// compiles
a.Register([] (int, double) {});
// doesn't
//a.Register([] (int, double) { return true; });
// works
a.Register(foo);
// doesn't
//a.Register(foo2);
}
你可以使用std::is_convertible(因为C ++ 11),例如
static_assert(std::is_convertible_v<Callable&&, std::function<Signature>>, "Wrong Signature!");
要么
static_assert(std::is_convertible_v<decltype(callable), decltype(callback)>, "Wrong Signature!");
在C ++ 17中有特质is_invocable<Callable, Args...>
,它完全符合你的要求。它优于is_convertible<std::function<Signature>,...>
的优点是您不必指定返回类型。这可能听起来有点矫枉过正,但最近我遇到了必须使用它的问题,我的包装函数确实从传递的Callable中推断出它的返回类型,但是我已经通过模板化的lambda像这样一个[](auto& x){return 2*x;}
,所以它的返回类型是在suball中推断出来的。我无法将其转换为std::function
,我最终使用is_invocable
本地实现C ++ 14。我无法找到我从中得到它的链接...无论如何,代码:
template <class F, class... Args>
struct is_invocable
{
template <class U>
static auto test(U* p) -> decltype((*p)(std::declval<Args>()...), void(), std::true_type());
template <class U>
static auto test(...) -> decltype(std::false_type());
static constexpr bool value = decltype(test<F>(0))::value;
};
并为您的例子:
struct A {
using Signature = void(int, double);
template <typename Callable>
void Register(Callable &&callable) {
static_assert(is_invocable<Callable,int,double>::value, "not foo(int,double)");
callback = callable;
}
std::function<Signature> callback;
};
如果您接受在可变参数模板类中转换A
,则可以使用decltype()
,仅在Register
兼容时激活callable
,如下所示
template <typename R, typename ... Args>
struct A
{
using Signature = R(Args...);
template <typename Callable>
auto Register (Callable && callable)
-> decltype( callable(std::declval<Args>()...), void() )
{ callback = callable; }
std::function<Signature> callback;
};
这样,如果您愿意,使用不兼容的函数调用Register()
,您可以获得软错误并激活另一个Register()
函数
void Register (...)
{ /* do something else */ };
你可以使用检测成语,这是一种形式的sfinae。我相信这适用于c ++ 11。
template <typename...>
using void_t = void;
template <typename Callable, typename enable=void>
struct callable_the_way_i_want : std::false_type {};
template <typename Callable>
struct callable_the_way_i_want <Callable, void_t <decltype (std::declval <Callable>()(int {},double {}))>> : std::true_type {};
然后你可以在你的代码中写一个静态断言,如下所示:
static_assert (is_callable_the_way_i_want <Callable>::value, "Not callable with required signature!");
这个优点超过我上面看到的答案是:
std::function
业务。例如,std::function
可能导致动态分配,否则将是不必要的。static_assert
对测试,并在那里放一个很好的人类可读的错误信息Tartan Llama撰写了一篇关于这项技术的精彩博文,以及几种替代方案,请查看! qazxsw poi
如果您需要这么做,那么您可能需要查看callable_traits库。
在这种情况下,您可以使用一个非常简单的库https://blog.tartanllama.xyz/detection-idiom/。
使用它的例子:
Boost.Callable Traits
要获得功能类型,您可以使用#include <boost/callable_traits.hpp>
#include <iostream>
#include <tuple>
template<typename F>
void register_handler(F&)
{
if constexpr (std::is_same_v<boost::callable_traits::function_type_t<F>, void(int&, double)>)
{
std::cout << "Register handler with signature void(int&, double)" << std::endl;
}
else if constexpr (std::is_same_v<boost::callable_traits::function_type_t<F>, void(int)>)
{
std::cout << "Register handler with signature void(int)" << std::endl;
}
}
void func(int&, double)
{}
auto lambda = [](int) {};
int main()
{
{
register_handler(func);
register_handler(lambda);
}
{
using function_type = boost::callable_traits::function_type_t<decltype(func)>;
using expected_function_type = void(int&, double);
std::cout << std::boolalpha << std::is_same_v<expected_function_type, function_type> << std::endl;
}
}
。
正如你在boost::callable_traits::function_type_t<decltype(func)>
和main
函数中看到的那样,可以使用register_handler
“function” - > expected_function_type
来比较boost::callable_traits::function_type_t<FUNCTION>
类型和函数类型(std::is_same_v
)
如果你想运行我的例子,请使用例如gcc 7.1.0使用boost 1.66.0和c ++ 17进行编译。 https://en.cppreference.com/w/cpp/types/is_same你可以在网上做:)