将 lambda 传递给可变参数时进行类型推导 std::function

Type deduction when passing lambda into variadic 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;
    });
}

我 运行 跨越 and ,这看起来很相似,但我相信我的情况不同,因为我需要可变类型以便在检索所需值时访问它们的特征。在那些例子中,函数的可变参数被视为 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 的类型中,所以我不确定如何在指南中提取它。

提供了一个实例 here

我不确定 identity 结构的用途,但删除它会给出更清晰的错误消息(模板推导失败)。

编译器无法从 lambda 派生出 std::function 类型。为了证明这一点,以下代码确实可以编译:

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

所以为了让它工作,我们只需要帮助编译器派生类型。借用http://www.cplusplus.com/forum/general/223816/以下作品:

namespace detail
{
    template < typename T > struct deduce_type;

    template < typename RETURN_TYPE, typename CLASS_TYPE, typename... ARGS >
    struct deduce_type< RETURN_TYPE(CLASS_TYPE::*)(ARGS...) const >
    {
        using type = std::function< RETURN_TYPE(ARGS...) >;
    };
}

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

    template<typename Lambda>
    void get(size_t index, Lambda l)
    {
        get( index, typename detail::deduce_type< decltype( &Lambda::operator() ) >::type( l ) );
    }

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

您标记了 C++17,因此您可以使用 std::function 的推导指南

因此,正如 Alan Birtles 所建议的,您可以将 lambda 作为简单类型接收,将其转换为 std::function(演绎指南)并推断出参数的类型。

有点像

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

    template <typename F>
    void get(size_t index, F f)
    { get(index, std::function{f}); }

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

您可以使用 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。这给了我们一些类型。在 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 合格的情况。

现在,这不适用于 any 可调用。如果您传入通用 lambda,推导 std::function 将失败。不过呢……反正也行不通,看来也不是什么大亏吧?