【问题标题】:Variadic template method and std::function - compilation error [duplicate]可变参数模板方法和 std::function - 编译错误 [重复]
【发布时间】:2015-08-24 16:42:02
【问题描述】:

我确信错误非常简单和愚蠢,但我看不到一个。代码如下:

#include <future>

template <typename ResultType>
class Foo
{
public:
    template <typename ...Args>
    void exec(const std::function<ResultType(Args...)>& task, Args&&... args) {}
};

int main()
{
   Foo<void>().exec([](){});
   return 0;
}

这是错误:

'void CAsyncTask::exec(const std::function &,Args &&...)' : 无法为 'const 推断模板参数 std::function &' 与 [ 结果类型=无效]

Foo&lt;void&gt;().exec&lt;void&gt;([](){}) 也不起作用(我更希望不必手动指定 Args 类型)。

关于建议答案的更新:以下代码确实有效。 CAsyncTask&lt;void&gt;().exec(std::function&lt;void ()&gt;([](){}));

但是这个问题真的没有解决方法吗?我可以以某种方式扩展我的模板来推断 lambda 参数吗?

【问题讨论】:

    标签: c++ templates c++11 variadic-templates


    【解决方案1】:

    您可以尝试将 exec 的签名更改为

    template<typename Fn, typename... Args> 
    void exec(Fn&& task, Args&&... args)
    

    然后在函数内部构造你的 std::function

    【讨论】:

    • 是的,刚刚得到了同样的想法,并且成功了!
    【解决方案2】:

    正如 Pradhan 提到的,std::function 的确切类型无法从 lambda 中推断出来。您可以明确地转换它来解决问题:

    Foo<void>().exec(std::function<void()>([](){}));
    

    或者,您可以简单地为函数使用另一个类型名,而无需创建std::function

    template <typename Callable, typename ...Args>
    void exec(Callable&& task, Args&&... args);
    

    然后Callable 将接受多种类型的函子,包括 lambda。

    请注意,与模板解决方案相比,使用 std::function 对象的性能会有所下降。

    您还可以添加static_assert 以确保可以使用给定参数调用您的Callable,否则会显示一条有意义的消息,而不是让编译器在实际调用时生成一条消息。

    【讨论】:

      【解决方案3】:

      lambda 不是std::function,因此无法从中推断出此类类型。您需要将 std::function 部分设为不可演绎的上下文:

      template<class T>struct identity_t{using type=T;};
      template<class T>using identity=typename identity_t<T>::type;
      
      template <typename ResultType>
      class Foo
      {
      public:
          template <typename... Args>
          void exec(const std::function<ResultType(identity<Args>...)>& task,
                    Args&&... args)
          {}
      };
      

      您还可以使用模板约束使函数完全通用;

      #include <type_traits>
      
      template<class...>struct voider{using type=void;};
      template<class... Ts>using void_t=typename voider<Ts...>::type;
      
      template<class T,class=void>
      struct callable:std::false_type{};
      template<class F,class... Ts>
      using invoker=decltype(std::declval<F>()(std::declval<Ts>()...));
      template<class F,class... Ts>
      struct callable<F(Ts...),void_t<invoker<F,Ts...>>>:std::true_type{};
      
      template<class R,class S>
      using result_eq=std::is_same<std::result_of_t<S>,R>;
      
      #define REQUIRE(cond) std::enable_if_t<(cond)>* = nullptr
      
      template<class R>
      class Foo
      {
      public:
          template<class F, class... Args, REQUIRE(callable<F(Args...)>{}), REQUIRE((result_eq<R, F(Args...)>{}))>
          void exec(F&& f, Args&&...);
      };
      
      int main()
      {
          Foo<void>().exec([] (int) {}, 4);
      }
      

      【讨论】:

      • 嗯。这是我第二次看到身份技巧了,还是没看懂……
      • @VioletGiraffe 我认为这与函数和结构/类不完全具有相同的模板推导规则这一事实有关。所以我们使用结构体来推断类型。
      • @VioletGiraffe 任何包含由模板参数(即void f(typename T::value_type))组成的嵌套名称说明符的参数都是非推导上下文。模板参数不会被传入的参数推导出来。模板别名identity 是为了更简洁的语法,但真正起作用的是identity_t。别名模板被替换为typename identity_t&lt;T&gt;::type,这是一个嵌套的名称说明符,因此使其成为非推导上下文。然后Args... 将被推导出为第二个参数包args...
      • @0x499602D2:啊哈!我想我现在明白了。谢谢!
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-07-27
      • 2022-01-05
      • 1970-01-01
      • 1970-01-01
      • 2015-12-09
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多