【问题标题】:Initialize class containing a std::function with a lambda使用 lambda 初始化包含 std::function 的类
【发布时间】:2013-11-12 00:32:06
【问题描述】:

我通过以下方式创建了一个包含std::function 作为成员的模板类:

template<typename Ret, typename... Args>
class Foo
{
private:
    std::function<Ret(Args...)> _func;

public:
    Foo(const std::function<Ret(Args...)>& func):
        _func(func)
    {}
};

为了不必指定传递函数的参数和返回类型,我创建了一些make_foo 重载:

template<typename Ret, typename... Args>
auto make_foo(Ret (&func)(Args...))
    -> Foo<Ret, Args...>
{
    return { std::function<Ret(Args...)>(func) };
}

template<typename Ret, typename... Args>
auto make_foo(const std::function<Ret(Args...)>& func)
    -> Foo<Ret, Args...>
{
    return { func };
}

但是,我无法创建以 lambda 作为参数的 make_foo 重载:

template<typename Ret, typename... Args>
auto make_foo(??? func)
    -> Foo<Ret, Args...>
{
    return { std::function<Ret(Args...)>(func) };
}

我只是找不到从 lambda 自动推导出返回类型和参数类型的方法。有没有一种惯用的方法来解决这样的问题?

【问题讨论】:

  • 你应该可以用T(或者F,如果你愿意的话)替换所有的东西。
  • 也许可以查看this Boost library
  • std::function 为参数的重载足以允许使用 lambda。事实上,您应该将它用作唯一的重载,因为它同时接受 lambda 和函数指针。
  • @0x499602D2 好吧,它does not seem to work 虽然:/
  • @0x499602D2:不适用于 lambda。用户定义的转化次数过多。

标签: c++ templates c++11 lambda std-function


【解决方案1】:

好吧,所以我以为我会死,但我终于设法做到了ç_ç

首先,我使用了通常的索引。由于我没有官方的索引,所以我使用了几个月前写的旧索引:

template<std::size_t...>
struct indices {};

template<std::size_t N, std::size_t... Ind>
struct make_indices:
    make_indices<N-1, N-1, Ind...>
{};

template<std::size_t... Ind>
struct make_indices<0, Ind...>:
    indices<Ind...>
{};

然后,我使用了一些在 StackOverflow 上找到的 function traits。它们很好,我认为它们相当于 cmets 中链接的 Boost 库:

template<typename T>
struct function_traits:
    function_traits<decltype(&T::operator())>
{};

template<typename C, typename Ret, typename... Args>
struct function_traits<Ret(C::*)(Args...) const>
{
    enum { arity = sizeof...(Args) };

    using result_type = Ret;

    template<std::size_t N>
    using arg = typename std::tuple_element<N, std::tuple<Args...>>::type;
};

然后,我能够编写一个正确的make_foo 函数和它的实现函数,因为两者都需要使用索引。小心,它很丑:

template<typename Function, std::size_t... Ind>
auto make_foo_(Function&& func, indices<Ind...>)
    -> Foo<
        typename function_traits<typename std::remove_reference<Function>::type>::result_type,
        typename function_traits<typename std::remove_reference<Function>::type>::template arg<Ind>...>
{
    using Ret = typename function_traits<typename std::remove_reference<Function>::type>::result_type;
    return { std::function<Ret(typename function_traits<typename std::remove_reference<Function>::type>::template arg<Ind>...)>(func) };
}

template<typename Function, typename Indices=make_indices<function_traits<typename std::remove_reference<Function>::type>::arity>>
auto make_foo(Function&& func)
    -> decltype(make_foo_(std::forward<Function>(func), Indices()))
{
    return make_foo_(std::forward<Function>(func), Indices());
}

代码有点丑陋且不可读,但它确实有效。希望它现在不依赖于某些实现定义的行为。另外,谢谢大家的建议,很有帮助! :)

int main()
{
    auto lambda = [](int i, float b, long c)
    {
        return long(i*10+b+c);
    };

    auto foo = make_foo(lambda);
    std::cout << foo(5, 5.0, 2) << std::endl; // 57, it works!
}

这里是live example :)

【讨论】:

  • 如果我早点发现this proposal,我就不会那么痛苦了……
  • Morwenn 在一周内进行两次救援......我碰巧需要这个。它似乎有效,但我在 "generic lambdas" 上看到了一些 FUD,类似的答案实际上并不适合我的用例。由于我很难理解此处所写内容的含义,因此我只会提及它并问“您认为通用 lambda 会破坏这一点吗?”(注意:另外,该提案链接已失效现在...)
  • @HostileFork 它可能会坏掉。虽然它可以正确处理具有固定类型的函数和 lambda,但一旦出现重载或模板,它就会中断,因为它 &amp;T::operator() 无法解析重载函数的地址。底线是,如果您可以抽象出函数的类型,那么请这样做,如果没有其他方法,您可以尝试使用function_traits(请注意,它们也存在于 Boost 中)。死链接是指向一个问题的链接,该问题也提供了一些功能特征。
【解决方案2】:

我有一个适用于 mutable lambdas 的示例。我不太清楚如何正确获得 CV 会员资格。

首先,这是我们需要的函数模板:

#include <functional>

template <typename R, typename ...Args>
void foo(std::function<R(Args...)> f)
{ }

现在我们将让函数模板bar 采用任意 lambda 并调用正确版本的 foo,通过检查 lambda 的 operator() 的类型:

#include <type_traits>

template <typename> struct remove_member;

template <typename C, typename T>
struct remove_member<T C::*>
{ using type = T; };

template <typename F>
void bar(F f)
{
    using ft = decltype(&F::operator());
    foo(std::function<typename remove_member<ft>::type>(f));
}

例子:

int q;
bar([&](int a, int b) mutable -> int { q = a + b; return q / b; });

你可以使用普通的 const lambdas 和这个修改过的特性,虽然我不喜欢把函数类型拼出来:

template <typename C, typename R, typename ...Args>
struct remove_member<R (C::*)(Args...) const>
{ using type = R(Args...); };

我认为如果我使用typename std::remove_cv&lt;T&gt;::type,它可能适用于原始代码,但至少在 GCC 上这不起作用,因为在 lambda 的运算符类型上设置了一些奇怪的 __attribute__((const)),这似乎干扰了模板专业化。

【讨论】:

  • 向反对者投票并回答你的挑战,尽管我使用了@Morwenn's solution(虽然可能有点令人费解)。我很好奇你对此的想法,如果"generic lambdas" 抛出任何曲线球,因为在我的简短调查中,他们被提到在解决这个问题时击败了一些黑客(尽管很难找到一个确切的问题没有人说“那不是你真正想要的”的地图).
  • @HostileFork:我想你不能在通用 lambda 上使用 decltype(&amp;F::operator()),因为该成员函数是一个模板。
猜你喜欢
  • 1970-01-01
  • 2018-03-14
  • 2017-04-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-09-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多