首页 > 解决方案 > 将 lambda 传递给可变参数 std::function 时的类型推导

问题描述

我正在尝试使用用于处理它们的函数的类型信息从数组元组中检索值。但是,在这种情况下,类型推导失败(部分原因是?)因为需要对std::function. 有没有办法在这里恢复扣除?

#include <functional>
#include <iostream>
#include <tuple>

class comp_a
{
public:
    static const size_t id = 0;

    int val = 0;
};

class comp_b
{
public:
    static const size_t id = 1;

    int val = 0;
};

class comp_c
{
public:
    static const size_t id = 2;

    int val = 0;
};

template<size_t size, typename ... Cs>
struct storage
{    
    template<typename T> struct identity { using type = T; };

    template<typename ... Ts>
    void get(size_t index, typename identity<std::function<void(Ts& ...)>>::type f)
    {
        f(std::get<Ts::id>(components)[index] ...);
    }

    std::tuple<std::array<Cs, size> ...> components;
};

int32_t main()
{
    storage<20, comp_a, comp_b, comp_c> storage;

    storage.get(2, [](comp_a& a, comp_c& c) {              // Doesn't work
    // storage.get<comp_a, comp_c>(2, [](comp_a& a, comp_c& c) { // Works
        std::cout << a.val << " " << c.val << std::endl;
    });
}

我遇到过thisthis,它们看起来很相似,但我相信我的情况有所不同,因为我需要可变参数类型才能在检索所需值时访问它们的特征。在这些示例中,函数的可变参数被视为 void:

error: cannot convert 'main()::<lambda(comp_a&, comp_c&)>' to 'storage<20, comp_a, comp_b, comp_c>::identity<std::function<void()> >::type' {aka 'std::function<void()>'}

在这种情况下,扣除指南是否可行?类型信息似乎隐藏在 的类型中有点深std::function,所以我不确定如何在指南中将其提取出来。

这里有一个活生生的例子。

标签: c++templatesc++17variadic-templatesstd-function

解决方案


您可以使用std::function演绎指南技巧来提取参数,但您真的不想实际创建一个std::function. 您想在 lambda-land 中堆叠。std::function由于类型擦除而增加了开销和分配 - 但是您所做的任何事情实际上都不需要类型擦除提供的好处。都是输,没有赢。实际上不要制作std::function.

也就是说,您当然仍然需要这些论点。所以你可以这样做:

template <typename T> struct type { };

template <typename F>
void get(size_t index, F f) {
    using function_type = decltype(std::function(f));
    get_impl(index, f, type<function_type>{});
}

基本上,我们采用一些可调用的 - 然后我们std::function从中推断出 a。这给了我们一些类型。在 OP 中的具体示例中,该类型是std::function<void(comp_a&, comp_b&)>. 然后,我们只需将该类型转发给不同的函数——作为一个空对象。没有开销。重复一遍,我们实际上并没有创建一个std::function- 我们只是传递它的类型。

该其他功能可以利用知道什么std::function知道:

template <typename T> using uncvref_t = std::remove_cv_t<std::remove_reference_t<T>>;

template <typename F, typename R, typename... Args>
void get_impl(size_t index, F f, type_t<std::function<R(Args...)>>) {
    f(std::get<uncvref_t<Args>::id>(components)[index] ...);
}

您需要uncvref_t那里来处理Args可能是也可能不是参考或cv合格的情况。

现在,这对任何可调用对象都不起作用。如果你传入一个通用的 lambda,推导std::function将失败。但是……那无论如何也行不通,所以看起来损失不大?


推荐阅读