【问题标题】:Type deduction when passing lambda into variadic std::function将 lambda 传递给可变参数 std::function 时的类型推导
【发布时间】:2019-01-17 04:34:13
【问题描述】:

我正在尝试使用用于处理它们的函数的类型信息从数组元组中检索值。但是,在这种情况下,类型推导失败(部分原因是?)因为需要为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 的类型中有点深,所以我不确定如何在指南中将其提取出来。

here 提供了一个实时示例。

【问题讨论】:

  • 我无法回答您的具体问题,但为什么要使用 std::function?你不能使用identity&lt;void(Ts&amp;...)&gt;::type(甚至void(Ts&amp;...)吗?
  • 遗憾的是,常规函数指针不起作用,因为 AFAIK 它排除了 lambda 内的任何捕获(我想做的事情,尽管在这个例子中我不这样做)。当我尝试它时,它似乎也失败了参数推导。通过身份运行它会出现同样的问题,即推导对函数参数视而不见。
  • 身份类型的一种用途正是您要避免的。它使您的论点不可演绎,因此必须在模板类型列表中指定它。所以我不确定它为什么在那里......

标签: c++ templates c++17 variadic-templates std-function


【解决方案1】:

您可以使用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&lt;void(comp_a&amp;, comp_b&amp;)&gt;。然后我们简单地将该类型转发给不同的函数——作为一个空对象。没有开销。重复一遍,我们实际上并没有创建 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 将失败。不过……反正这样也行不通,看来损失不大?

【讨论】:

  • Surprised and impressed at how well this optimizes. 你介意向我解释一下type&lt;T&gt; 在这里的作用以及为什么它是必要的吗?
  • @rtek 我们需要std::function 的类型来推导出参数,但我们不想创建该类型的对象。 type&lt;T&gt; 只是一种传递类型的方法
【解决方案2】:

我不确定 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;
};

【讨论】:

    【解决方案3】:

    您标记了 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::remove_cv_t&lt;Ts&gt;::id.
    • @Jarod42 - 嗯……为什么?我的意思是:没有它会有什么问题?
    • 处理std::function&lt;void(const Ta&amp;, Tb&amp;)&gt;
    猜你喜欢
    • 1970-01-01
    • 2015-12-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多