将任何 lambda 函数(包括捕获 lambda 表达式)转换为 std::function 对象的模板
Template to convert any lambda function (including capturing lambdas) to a std::function object
我有以下代码可以将 lambda 转换为 C 风格的函数指针。这适用于所有 lambda,包括带捕获的 lambda。
#include <iostream>
#include <type_traits>
#include <utility>
template <typename Lambda>
struct lambda_traits : lambda_traits<decltype(&Lambda::operator())>
{};
template <typename Lambda, typename Return, typename... Args>
struct lambda_traits<Return(Lambda::*)(Args...)> : lambda_traits<Return(Lambda::*)(Args...) const>
{};
template <typename Lambda, typename Return, typename... Args>
struct lambda_traits<Return(Lambda::*)(Args...) const>
{
using pointer = typename std::add_pointer<Return(Args...)>::type;
static pointer to_pointer(Lambda&& lambda)
{
static Lambda static_lambda = std::forward<Lambda>(lambda);
return [](Args... args){
return static_lambda(std::forward<Args>(args)...);
};
}
};
template <typename Lambda>
inline typename lambda_traits<Lambda>::pointer to_pointer(Lambda&& lambda)
{
return lambda_traits<Lambda>::to_pointer(std::forward<Lambda>(lambda));
}
这可以按如下方式使用,将带有捕获的 lambda 传递给 C 风格 API:
// Function that takes a C-style function pointer as an argument
void call_function(void(*function)())
{
(*function)();
}
int main()
{
int x = 42;
// Pass the lambda to the C-style API
// This works even though the lambda captures 'x'!
call_function(to_pointer([x] {
std::cout << x << std::endl;
}));
}
鉴于此,编写一个可以将 lambda(包括带有捕获的 lambda)一般转换为 std::function
对象的类似模板似乎应该相对简单,但我正在努力弄清楚如何做。 (我对模板元编程技术不是很熟悉所以我有点迷茫)
这是我试过的,但是编译失败:
template <typename Lambda>
struct lambda_traits : lambda_traits<decltype(&Lambda::operator())>
{};
template <typename Lambda, typename Return, typename... Args>
struct lambda_traits<typename std::function<Return(Args...)>> : lambda_traits<typename std::function<Return(Args...)> const>
{};
template <typename Lambda, typename Return, typename... Args>
struct lambda_traits<typename std::function<Return(Args...)> const>
{
using pointer = typename std::function<Return(Args...)>*;
static pointer to_pointer(Lambda&& lambda)
{
static Lambda static_lambda = std::forward<Lambda>(lambda);
return [](Args... args) {
return static_lambda(std::forward<Args>(args)...);
};
}
};
template <typename Lambda>
inline typename lambda_traits<Lambda>::pointer to_pointer(Lambda&& lambda)
{
return lambda_traits<Lambda>::to_pointer(std::forward<Lambda>(lambda));
}
编译失败,表示 Lambda
模板参数未被偏特化使用。
正确的做法是什么?
(注意,我一直在使用兼容 C++11 的编译器,因此无法使用 C++14 及更高版本的功能)
如果您想在不指定 std::function
的签名的情况下将可调用对象转换为 std::function
,这正是 C++17's deduction guides for std::function
的用途。我们只需要为 C++11 实现一个版本。请注意,这仅适用于具有非重载 operator()
的可调用对象;否则,没有办法做到这一点。
#include <functional>
#include <utility> // std::declval
// Using these functions just for the return types, so they don't need an implementation.
// Support function pointers
template <typename R, typename... ArgTypes>
auto deduce_std_function(R(*)(ArgTypes...)) -> std::function<R(ArgTypes...)>;
// Support callables (note the _impl on the name).
// Many overloads of this to support different const qualifiers and
// ref qualifiers. Technically should also support volatile, but that
// doubles the number of overloads and isn't needed for this illustration.
template <typename F, typename R, typename... ArgTypes>
auto deduce_std_function_impl(R(F::*)(ArgTypes...)) -> std::function<R(ArgTypes...)>;
template <typename F, typename R, typename... ArgTypes>
auto deduce_std_function_impl(R(F::*)(ArgTypes...) const) -> std::function<R(ArgTypes...)>;
template <typename F, typename R, typename... ArgTypes>
auto deduce_std_function_impl(R(F::*)(ArgTypes...) &) -> std::function<R(ArgTypes...)>;
template <typename F, typename R, typename... ArgTypes>
auto deduce_std_function_impl(R(F::*)(ArgTypes...) const&) -> std::function<R(ArgTypes...)>;
template <typename F, typename R, typename... ArgTypes>
auto deduce_std_function_impl(R(F::*)(ArgTypes...) &&) -> std::function<R(ArgTypes...)>;
template <typename F, typename R, typename... ArgTypes>
auto deduce_std_function_impl(R(F::*)(ArgTypes...) const&&) -> std::function<R(ArgTypes...)>;
// To deduce the function type for a callable, get its operator() and pass that to
// the _impl functions above.
template <typename Function>
auto deduce_std_function(Function)
-> decltype(deduce_std_function_impl(&Function::operator()));
template <typename Function>
using deduce_std_function_t = decltype(deduce_std_function(std::declval<Function>()));
template <typename F>
auto to_std_function(F&& fn) -> deduce_std_function_t<F> {
return deduce_std_function_t<F>(std::forward<F>(fn));
}
更详细的解释
我们需要推断 std::function<...>
的函数类型。所以我们需要实现某种 deduce_std_function
来确定函数类型。实现这个有几个选项:
- 创建一个
function_traits
类型,为我们计算函数类型(类似于您的 lambda_traits
)。
- 实现
deduce_std_function
作为重载集,其中重载的 return 类型是推导类型。
我选择了后者,因为它模仿了演绎指南。前者也可以,但我认为这种方法可能更容易(函数样板比结构样板小)。
简单案例
查看 std::function
推导指南的文档,有一个简单的:
template<class R, class... ArgTypes>
function(R(*)(ArgTypes...)) -> function<R(ArgTypes...)>;
这很容易翻译:
template <typename R, typename... ArgTypes>
auto deduce_std_function(R(*)(ArgTypes...)) -> std::function<R(ArgTypes...)>;
基本上,给定任何函数指针R(*)(ArgTypes...)
,我们想要的类型是std::function<R(ArgTypes...)>
。
更棘手的案例
文档将第二种情况描述为:
This overload only participates in overload resolution if
&F::operator()
is well-formed when treated as an unevaluated operand
and decltype(&F::operator())
is of the form R(G::*)(A...)
(optionally
cv-qualified, optionally noexcept, optionally lvalue reference
qualified) for some class type G. The deduced type is
std::function<R(A...)>
.
那是一口。然而,这里的关键思想是碎片:
- "
decltype(&F::operator())
的格式为 R(G::*)(A...)
"
- “推导的类型是
std::function<R(A...)>
”
这意味着我们需要获取 operator()
的成员函数指针,并将该成员函数指针的签名用作 std::function
的签名。
这就是它的来源:
template <typename Function>
auto deduce_std_function(Function)
-> decltype(deduce_std_function_impl(&Function::operator()));
我们委托给 deduce_std_function_impl
因为我们需要推断成员函数指针的签名 &Function::operator()
.
该 impl 函数的有趣重载是:
template <typename F, typename R, typename... ArgTypes>
auto deduce_std_function_impl(R(F::*)(ArgTypes...)) -> std::function<R(ArgTypes...)>;
简而言之,我们正在获取成员函数指针的签名(R ... (ArgTypes...)
位)并将其用于 std::function
。语法的其余部分((F::*)
位)只是成员函数指针的语法。 R(F::*)(ArgTypes...)
是 class F
的成员函数指针类型,签名为 R(ArgTypes...)
,没有 const、volatile 或引用限定符。
但是等等!我们希望支持 const 和引用限定符(您可能也希望添加对 volatile 的支持)。所以我们需要复制上面的 deduce_std_function_impl
,每个限定符一次:
Signature
Class Declaration
R(F::*)(ArgTypes...)
void operator()();
R(F::*)(ArgTypes...) const
void operator()() const;
R(F::*)(ArgTypes...) &
void operator()() &;
R(F::*)(ArgTypes...) const&
void operator()() const&;
R(F::*)(ArgTypes...) &&
void operator()() &&;
R(F::*)(ArgTypes...) const&&
void operator()() const&&;
我有以下代码可以将 lambda 转换为 C 风格的函数指针。这适用于所有 lambda,包括带捕获的 lambda。
#include <iostream>
#include <type_traits>
#include <utility>
template <typename Lambda>
struct lambda_traits : lambda_traits<decltype(&Lambda::operator())>
{};
template <typename Lambda, typename Return, typename... Args>
struct lambda_traits<Return(Lambda::*)(Args...)> : lambda_traits<Return(Lambda::*)(Args...) const>
{};
template <typename Lambda, typename Return, typename... Args>
struct lambda_traits<Return(Lambda::*)(Args...) const>
{
using pointer = typename std::add_pointer<Return(Args...)>::type;
static pointer to_pointer(Lambda&& lambda)
{
static Lambda static_lambda = std::forward<Lambda>(lambda);
return [](Args... args){
return static_lambda(std::forward<Args>(args)...);
};
}
};
template <typename Lambda>
inline typename lambda_traits<Lambda>::pointer to_pointer(Lambda&& lambda)
{
return lambda_traits<Lambda>::to_pointer(std::forward<Lambda>(lambda));
}
这可以按如下方式使用,将带有捕获的 lambda 传递给 C 风格 API:
// Function that takes a C-style function pointer as an argument
void call_function(void(*function)())
{
(*function)();
}
int main()
{
int x = 42;
// Pass the lambda to the C-style API
// This works even though the lambda captures 'x'!
call_function(to_pointer([x] {
std::cout << x << std::endl;
}));
}
鉴于此,编写一个可以将 lambda(包括带有捕获的 lambda)一般转换为 std::function
对象的类似模板似乎应该相对简单,但我正在努力弄清楚如何做。 (我对模板元编程技术不是很熟悉所以我有点迷茫)
这是我试过的,但是编译失败:
template <typename Lambda>
struct lambda_traits : lambda_traits<decltype(&Lambda::operator())>
{};
template <typename Lambda, typename Return, typename... Args>
struct lambda_traits<typename std::function<Return(Args...)>> : lambda_traits<typename std::function<Return(Args...)> const>
{};
template <typename Lambda, typename Return, typename... Args>
struct lambda_traits<typename std::function<Return(Args...)> const>
{
using pointer = typename std::function<Return(Args...)>*;
static pointer to_pointer(Lambda&& lambda)
{
static Lambda static_lambda = std::forward<Lambda>(lambda);
return [](Args... args) {
return static_lambda(std::forward<Args>(args)...);
};
}
};
template <typename Lambda>
inline typename lambda_traits<Lambda>::pointer to_pointer(Lambda&& lambda)
{
return lambda_traits<Lambda>::to_pointer(std::forward<Lambda>(lambda));
}
编译失败,表示 Lambda
模板参数未被偏特化使用。
正确的做法是什么?
(注意,我一直在使用兼容 C++11 的编译器,因此无法使用 C++14 及更高版本的功能)
如果您想在不指定 std::function
的签名的情况下将可调用对象转换为 std::function
,这正是 C++17's deduction guides for std::function
的用途。我们只需要为 C++11 实现一个版本。请注意,这仅适用于具有非重载 operator()
的可调用对象;否则,没有办法做到这一点。
#include <functional>
#include <utility> // std::declval
// Using these functions just for the return types, so they don't need an implementation.
// Support function pointers
template <typename R, typename... ArgTypes>
auto deduce_std_function(R(*)(ArgTypes...)) -> std::function<R(ArgTypes...)>;
// Support callables (note the _impl on the name).
// Many overloads of this to support different const qualifiers and
// ref qualifiers. Technically should also support volatile, but that
// doubles the number of overloads and isn't needed for this illustration.
template <typename F, typename R, typename... ArgTypes>
auto deduce_std_function_impl(R(F::*)(ArgTypes...)) -> std::function<R(ArgTypes...)>;
template <typename F, typename R, typename... ArgTypes>
auto deduce_std_function_impl(R(F::*)(ArgTypes...) const) -> std::function<R(ArgTypes...)>;
template <typename F, typename R, typename... ArgTypes>
auto deduce_std_function_impl(R(F::*)(ArgTypes...) &) -> std::function<R(ArgTypes...)>;
template <typename F, typename R, typename... ArgTypes>
auto deduce_std_function_impl(R(F::*)(ArgTypes...) const&) -> std::function<R(ArgTypes...)>;
template <typename F, typename R, typename... ArgTypes>
auto deduce_std_function_impl(R(F::*)(ArgTypes...) &&) -> std::function<R(ArgTypes...)>;
template <typename F, typename R, typename... ArgTypes>
auto deduce_std_function_impl(R(F::*)(ArgTypes...) const&&) -> std::function<R(ArgTypes...)>;
// To deduce the function type for a callable, get its operator() and pass that to
// the _impl functions above.
template <typename Function>
auto deduce_std_function(Function)
-> decltype(deduce_std_function_impl(&Function::operator()));
template <typename Function>
using deduce_std_function_t = decltype(deduce_std_function(std::declval<Function>()));
template <typename F>
auto to_std_function(F&& fn) -> deduce_std_function_t<F> {
return deduce_std_function_t<F>(std::forward<F>(fn));
}
更详细的解释
我们需要推断 std::function<...>
的函数类型。所以我们需要实现某种 deduce_std_function
来确定函数类型。实现这个有几个选项:
- 创建一个
function_traits
类型,为我们计算函数类型(类似于您的lambda_traits
)。 - 实现
deduce_std_function
作为重载集,其中重载的 return 类型是推导类型。
我选择了后者,因为它模仿了演绎指南。前者也可以,但我认为这种方法可能更容易(函数样板比结构样板小)。
简单案例
查看 std::function
推导指南的文档,有一个简单的:
template<class R, class... ArgTypes> function(R(*)(ArgTypes...)) -> function<R(ArgTypes...)>;
这很容易翻译:
template <typename R, typename... ArgTypes>
auto deduce_std_function(R(*)(ArgTypes...)) -> std::function<R(ArgTypes...)>;
基本上,给定任何函数指针R(*)(ArgTypes...)
,我们想要的类型是std::function<R(ArgTypes...)>
。
更棘手的案例
文档将第二种情况描述为:
This overload only participates in overload resolution if
&F::operator()
is well-formed when treated as an unevaluated operand anddecltype(&F::operator())
is of the formR(G::*)(A...)
(optionally cv-qualified, optionally noexcept, optionally lvalue reference qualified) for some class type G. The deduced type isstd::function<R(A...)>
.
那是一口。然而,这里的关键思想是碎片:
- "
decltype(&F::operator())
的格式为R(G::*)(A...)
" - “推导的类型是
std::function<R(A...)>
”
这意味着我们需要获取 operator()
的成员函数指针,并将该成员函数指针的签名用作 std::function
的签名。
这就是它的来源:
template <typename Function>
auto deduce_std_function(Function)
-> decltype(deduce_std_function_impl(&Function::operator()));
我们委托给 deduce_std_function_impl
因为我们需要推断成员函数指针的签名 &Function::operator()
.
该 impl 函数的有趣重载是:
template <typename F, typename R, typename... ArgTypes>
auto deduce_std_function_impl(R(F::*)(ArgTypes...)) -> std::function<R(ArgTypes...)>;
简而言之,我们正在获取成员函数指针的签名(R ... (ArgTypes...)
位)并将其用于 std::function
。语法的其余部分((F::*)
位)只是成员函数指针的语法。 R(F::*)(ArgTypes...)
是 class F
的成员函数指针类型,签名为 R(ArgTypes...)
,没有 const、volatile 或引用限定符。
但是等等!我们希望支持 const 和引用限定符(您可能也希望添加对 volatile 的支持)。所以我们需要复制上面的 deduce_std_function_impl
,每个限定符一次:
Signature | Class Declaration |
---|---|
R(F::*)(ArgTypes...) |
void operator()(); |
R(F::*)(ArgTypes...) const |
void operator()() const; |
R(F::*)(ArgTypes...) & |
void operator()() &; |
R(F::*)(ArgTypes...) const& |
void operator()() const&; |
R(F::*)(ArgTypes...) && |
void operator()() &&; |
R(F::*)(ArgTypes...) const&& |
void operator()() const&&; |