从可调用类型中提取签名
Extract signature from callable type
我有一个 std::variant<char, int, double>
类型的值。事实上,我有几个值存储在 std::vector<value>
.
我希望能够将这些值传递给多个 callback
函数,前提是它们的签名与值的类型匹配。这是我的实现:
using value = std::variant<char, int, double>;
using values = std::vector<value>;
template<typename... Args>
using callable = std::function<void (Args...)>;
struct callback
{
template<typename... Args>
callback(callable<Args...> fn)
{
constexpr auto N = sizeof...(Args);
fn_ = adapt(std::move(fn), std::make_index_sequence<N>());
}
void operator()(const values& vv) const { fn_(vv); }
private:
std::function<void (const values&)> fn_;
template<typename... Args, std::size_t... Is>
auto adapt(callable<Args...> fn, std::index_sequence<Is...>)
{
return [fn_ = std::move(fn)](const values& vv) {
/* if signature matches */ fn_(std::get<Args>(vv[Is])...);
};
}
};
using callbacks = std::vector<callback>;
现在我可以做到了,而且效果很好:
void fn(char c, int i) { std::cout << "fn:" << c << i << '\n'; }
int main()
{
values vv = { 'x', 42 };
callback cb1{ std::function<void (char, int)>(fn) };
callback cb2{ std::function<void (char, int)>([](char c, int i) { std::cout << "lambda:" << c << i << '\n'; }) };
callbacks cbs = { cb1, cb2 };
for(auto const& cb : cbs) cb(vv);
return 0;
}
你可以看到工作示例here。
我已经回顾了几个关于将函数指针、lambda、仿函数等隐式转换为 std::function
的(不)可能性的类似问题。
所以我的问题是,我需要什么“胶水”才能做到这一点:
callback cb3 { fn };
callback cb4 { [](char c, int i) { } };
// same for other callable types
我想到了某种 function_traits
实现来从可调用类型中提取签名并将其传递给 std::function
。但是查看 std::is_function 的代码让我觉得这需要大量样板代码。
在这里想请问有没有人能想出更简洁的解决方案。提前谢谢你。
更新
我用 汗水、血和泪水 部分专业化、SFINAE 和 void_t trick 完成了这项工作。我现在可以这样做了:
callback cb1 { fn };
callback cb2 { lambda };
callback cb3 { [](char c, int i) { std::cout << "lambda2:" << c << i << '\n'; } };
int x = 69;
callback cb4 { [x](char c, int i) { std::cout << "lambda3[x]:" << x << c << i << '\n'; } };
callback cb5 { ftor(x) };
这里是demo。在此实现中 callback
可以接受函数指针、lambda(包括捕获,但不是泛型)和函子。
至少对于您描述的用例,您可以通过添加额外的 callback
构造函数模板来实现您想要的。
template<typename... Args>
callback(void (*fn)(Args...)) : callback(callable<Args...>(fn)) {}
此构造函数将匹配一个可以衰减为函数指针的参数,例如此构造函数调用现在有效
callback cb3{ fn }; // ok
请注意,这个新的构造函数只是通过将函数指针显式转换为 callback<Args...>
.
来委托给您的原始构造函数
同样,您可以编写一个模板来匹配 lambda 参数。
template<typename Lambda>
callback(Lambda fn) : callback(+fn) {}
现在这个构造函数调用有效了。
callback cb4{ [](char c, int i) { std::cout << "lambda:" << c << i << '\n'; } }; // ok
这里注意,委托是给构造函数获取一个函数指针。这是通过使用一元 +
运算符将 lambda 显式衰减为函数指针来实现的。当然,该构造函数将进一步委托给您的原始构造函数进行实际实现。
这里是 demo。
正如 d_kog 评论的那样,问题本身在 C++ 中很少是正确的。可调用对象不需要有唯一的签名。因此,您应该重新定义问题。不要尝试确定签名来检查调用能力,而是直接检查调用能力。
您的回调必须使用值向量执行三项基本操作:
- 将大小“提升”到编译时,例如将向量转换为元组或包。
- 访问值,获取变体中的值。
- 如果可能,将函数应用于它们。
第 2 步和第 3 步很简单。 “查找签名”用于查找元数,以便索引序列和 vv[Is]...
为您提供一个包(假设首先检查 vv 的 size())。然后你使用 get<Args>
本质上是为了实现 visit
,但这是不必要的。
但是步骤1还有其他选项。例如,如果你只支持一定数量的参数,这是有效的:
template<typename F>
void call_helper(const values& vv, F&& f)
{
switch (vv.size()) {
case 0: std::forward<F>(f)(); break;
case 1: std::forward<F>(f)(vv[0]); break;
case 2: std::forward<F>(f)(vv[0], vv[1]); break;
case 3: std::forward<F>(f)(vv[0], vv[1], vv[2]); break;
default: throw std::out_of_range{"too many arguments to callback"};
}
}
然后adapt
变成:
return [fn_ = std::move(fn)](const values& vv) {
// lift from the vector to function arguments
call_helper(vv, [&](const auto&... args) {
// visit the arguments
std::visit([&](const auto&... args_unwrapped) {
// check invocability with the arguments
if constexpr (std::is_invocable_v<decltype(fn_), decltype(args_unwrapped)...>)
// invoke
std::invoke(fn_, args_unwrapped...);
else
throw std::runtime_error{"invalid argument types to callback"};
}, args...);
});
};
然后,您可以支持所有方式的 C++ 可调用对象:
callback cb1{ fn }; // function pointer
callback cb2{ [](char c, int i) { std::cout << "lambda:" << c << i << '\n'; } };
callback cb3{ [](auto c, auto i) { std::cout << "generic lambda:" << c << i << '\n'; }};
int j = 1000;
callback cb4{ [j](char c, int i) { std::cout << "capturing lambda[" << j << "]:" << c << i << '\n'; }};
callback cb5{ [](int c, int i) { std::cout << "implicit conversion allowed:" << c << i << '\n'; }};
callback cb6{ [] { /* "invalid argument types to callback" */ }};
我有一个 std::variant<char, int, double>
类型的值。事实上,我有几个值存储在 std::vector<value>
.
我希望能够将这些值传递给多个 callback
函数,前提是它们的签名与值的类型匹配。这是我的实现:
using value = std::variant<char, int, double>;
using values = std::vector<value>;
template<typename... Args>
using callable = std::function<void (Args...)>;
struct callback
{
template<typename... Args>
callback(callable<Args...> fn)
{
constexpr auto N = sizeof...(Args);
fn_ = adapt(std::move(fn), std::make_index_sequence<N>());
}
void operator()(const values& vv) const { fn_(vv); }
private:
std::function<void (const values&)> fn_;
template<typename... Args, std::size_t... Is>
auto adapt(callable<Args...> fn, std::index_sequence<Is...>)
{
return [fn_ = std::move(fn)](const values& vv) {
/* if signature matches */ fn_(std::get<Args>(vv[Is])...);
};
}
};
using callbacks = std::vector<callback>;
现在我可以做到了,而且效果很好:
void fn(char c, int i) { std::cout << "fn:" << c << i << '\n'; }
int main()
{
values vv = { 'x', 42 };
callback cb1{ std::function<void (char, int)>(fn) };
callback cb2{ std::function<void (char, int)>([](char c, int i) { std::cout << "lambda:" << c << i << '\n'; }) };
callbacks cbs = { cb1, cb2 };
for(auto const& cb : cbs) cb(vv);
return 0;
}
你可以看到工作示例here。
我已经回顾了几个关于将函数指针、lambda、仿函数等隐式转换为 std::function
的(不)可能性的类似问题。
所以我的问题是,我需要什么“胶水”才能做到这一点:
callback cb3 { fn };
callback cb4 { [](char c, int i) { } };
// same for other callable types
我想到了某种 function_traits
实现来从可调用类型中提取签名并将其传递给 std::function
。但是查看 std::is_function 的代码让我觉得这需要大量样板代码。
在这里想请问有没有人能想出更简洁的解决方案。提前谢谢你。
更新
我用 汗水、血和泪水 部分专业化、SFINAE 和 void_t trick 完成了这项工作。我现在可以这样做了:
callback cb1 { fn };
callback cb2 { lambda };
callback cb3 { [](char c, int i) { std::cout << "lambda2:" << c << i << '\n'; } };
int x = 69;
callback cb4 { [x](char c, int i) { std::cout << "lambda3[x]:" << x << c << i << '\n'; } };
callback cb5 { ftor(x) };
这里是demo。在此实现中 callback
可以接受函数指针、lambda(包括捕获,但不是泛型)和函子。
至少对于您描述的用例,您可以通过添加额外的 callback
构造函数模板来实现您想要的。
template<typename... Args>
callback(void (*fn)(Args...)) : callback(callable<Args...>(fn)) {}
此构造函数将匹配一个可以衰减为函数指针的参数,例如此构造函数调用现在有效
callback cb3{ fn }; // ok
请注意,这个新的构造函数只是通过将函数指针显式转换为 callback<Args...>
.
同样,您可以编写一个模板来匹配 lambda 参数。
template<typename Lambda>
callback(Lambda fn) : callback(+fn) {}
现在这个构造函数调用有效了。
callback cb4{ [](char c, int i) { std::cout << "lambda:" << c << i << '\n'; } }; // ok
这里注意,委托是给构造函数获取一个函数指针。这是通过使用一元 +
运算符将 lambda 显式衰减为函数指针来实现的。当然,该构造函数将进一步委托给您的原始构造函数进行实际实现。
这里是 demo。
正如 d_kog 评论的那样,问题本身在 C++ 中很少是正确的。可调用对象不需要有唯一的签名。因此,您应该重新定义问题。不要尝试确定签名来检查调用能力,而是直接检查调用能力。
您的回调必须使用值向量执行三项基本操作:
- 将大小“提升”到编译时,例如将向量转换为元组或包。
- 访问值,获取变体中的值。
- 如果可能,将函数应用于它们。
第 2 步和第 3 步很简单。 “查找签名”用于查找元数,以便索引序列和 vv[Is]...
为您提供一个包(假设首先检查 vv 的 size())。然后你使用 get<Args>
本质上是为了实现 visit
,但这是不必要的。
但是步骤1还有其他选项。例如,如果你只支持一定数量的参数,这是有效的:
template<typename F>
void call_helper(const values& vv, F&& f)
{
switch (vv.size()) {
case 0: std::forward<F>(f)(); break;
case 1: std::forward<F>(f)(vv[0]); break;
case 2: std::forward<F>(f)(vv[0], vv[1]); break;
case 3: std::forward<F>(f)(vv[0], vv[1], vv[2]); break;
default: throw std::out_of_range{"too many arguments to callback"};
}
}
然后adapt
变成:
return [fn_ = std::move(fn)](const values& vv) {
// lift from the vector to function arguments
call_helper(vv, [&](const auto&... args) {
// visit the arguments
std::visit([&](const auto&... args_unwrapped) {
// check invocability with the arguments
if constexpr (std::is_invocable_v<decltype(fn_), decltype(args_unwrapped)...>)
// invoke
std::invoke(fn_, args_unwrapped...);
else
throw std::runtime_error{"invalid argument types to callback"};
}, args...);
});
};
然后,您可以支持所有方式的 C++ 可调用对象:
callback cb1{ fn }; // function pointer
callback cb2{ [](char c, int i) { std::cout << "lambda:" << c << i << '\n'; } };
callback cb3{ [](auto c, auto i) { std::cout << "generic lambda:" << c << i << '\n'; }};
int j = 1000;
callback cb4{ [j](char c, int i) { std::cout << "capturing lambda[" << j << "]:" << c << i << '\n'; }};
callback cb5{ [](int c, int i) { std::cout << "implicit conversion allowed:" << c << i << '\n'; }};
callback cb6{ [] { /* "invalid argument types to callback" */ }};