【问题标题】:Template to convert any lambda function (including capturing lambdas) to a std::function object将任何 lambda 函数(包括捕获 lambda)转换为 std::function 对象的模板
【发布时间】:2021-05-08 07:05:14
【问题描述】:

我有以下代码可以将 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;
        }));
}

鉴于此,编写一个可以将 lambdas(包括带有捕获的 lambdas)一般转换为 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 及更高版本的功能)

【问题讨论】:

  • "我有以下代码可以将 lambda 转换为 C 风格的函数指针。"非常危险。如果您使用该代码将相同类型的两个可调用对象转换为函数指针,则第二个函数指针的行为将与第一个函数指针完全相同,而忽略状态(捕获)的任何差异。这是一个坏主意。另见codereview.stackexchange.com/a/255187/49895
  • 你在找C++17的std::function(lambda)(即std::function的类模板参数推导)吗?
  • 是的,这看起来确实像我需要的......但我被困在使用较旧的编译器,所以我只能使用 C++11 功能。有没有办法在 C++11 中获得这种行为?

标签: c++ c++11 lambda function-pointers template-meta-programming


【解决方案1】:

如果您想将可调用对象转换为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));
}

Demo


更详细的解释

我们需要推导出std::function&lt;...&gt; 的函数类型。所以我们需要实现某种deduce_std_function 来确定函数类型。有几种实现方式:

  • 创建一个function_traits 类型,它可以为我们确定函数类型(类似于您的lambda_traits)。
  • deduce_std_function 实现为重载集,其中重载的返回类型是推导的类型。

我选择后者是因为它模仿了演绎指南。前者也可以,但我认为这种方法可能更容易(函数样板小于结构样板)。

简单案例

查看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&lt;R(ArgTypes...)&gt;

更棘手的情况

文档将第二种情况描述为:

此重载仅在以下情况下参与重载决议 &amp;F::operator() 被视为未计算的操作数时格式正确 decltype(&amp;F::operator()) 的形式为 R(G::*)(A...)(可选 cv 限定,可选 noexcept,可选左值引用 合格)对于某些类类型 G。推导的类型是 std::function&lt;R(A...)&gt;.

这是一口。然而,这里的关键思想是碎片:

  • "decltype(&amp;F::operator()) 的形式为 R(G::*)(A...)"
  • "推导出的类型是std::function&lt;R(A...)&gt;"

这意味着我们需要获取operator() 的指向成员函数的指针,并将该指向成员函数的签名用作std::function 的签名。

这就是它的来源:

template <typename Function>
auto deduce_std_function(Function)
    -> decltype(deduce_std_function_impl(&Function::operator()));

我们委托给deduce_std_function_impl,因为我们需要推导出指向成员函数&amp;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...)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...) &amp; void operator()() &amp;;
R(F::*)(ArgTypes...) const&amp; void operator()() const&amp;;
R(F::*)(ArgTypes...) &amp;&amp; void operator()() &amp;&amp;;
R(F::*)(ArgTypes...) const&amp;&amp; void operator()() const&amp;&amp;;

【讨论】:

  • 成功了,你是冠军!太感谢了!现在我只需要在接下来的几天里弄清楚这是如何工作的,哈哈。
  • 感谢您的深入解释,我有很多东西要学习模板元编程,所以这一切都非常有帮助。 :)
  • @tjwrona1992 没问题!我想如果你必须花几天时间来了解它,深入的解释可能会有所帮助。很高兴看到它很有帮助。
猜你喜欢
  • 1970-01-01
  • 2013-12-15
  • 1970-01-01
  • 1970-01-01
  • 2021-06-30
  • 2021-02-20
  • 1970-01-01
  • 2016-04-18
相关资源
最近更新 更多