c++ - 将任何 lambda 函数(包括捕获 lambda)转换为 std::function 对象的模板

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

我有以下代码可以将 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 及更高版本的功能不可用)

最佳答案

如果要将可调用对象转换为 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<...> 的函数类型。 .所以我们需要实现某种deduce_std_function找出函数类型。有几个选项可以实现这一点:
  • 制作 function_traits type 为我们计算出函数类型(类似于您的 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<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()));
    
    我们委托(delegate)给 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...)是类 F 的成员函数指针的类型带有签名R(ArgTypes...)并且没有 const、volatile 或引用限定符。
    可是等等!我们希望支持 const 和引用限定符(您也可能希望添加对 volatile 的支持)。所以我们需要复制deduce_std_function_impl上面,每个限定符一次:


    签名
    类声明

    R(F::*)(ArgTypes...)void operator()();R(F::*)(ArgTypes...) constvoid 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&&;

    关于c++ - 将任何 lambda 函数(包括捕获 lambda)转换为 std::function 对象的模板,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66037864/

    相关文章:

    c++ - 从 C++ 中的另一个线程读取指针

    c++ - 使用用户定义的比较类对 std::pair 的 std::vector 进行排序

    python - 如何将具有动态维度的张量的每个 2D 元素乘以静态 2D 掩码?

    python - 应用和 Lambda 函数 - ValueError : The truth value of a Series is ambiguous. 使用 a.empty、a.bool()、a.item()、a.any() 或 a.all()

    c++ - 在 C++ 中继承具有相似签名的成员函数时出现问题

    c++ - CSS设置QTableView背景色?

    c++ - 如何使用 boost::sprit 来解析嵌套的 for 循环?

    c++ - Qt 5.1 for OSX 安装仅包含 clang_64 目录,如何使用 macports gcc 进行编译?

    c++ - std::allocate_shared,允许从自定义分配器和单次分配中分配共享指针引用计数

    c# - 用于迭代集合并编辑 C# 的 Lambda 表达式