将函数传递给可变函数模板

Passing a function to a variadic function template

考虑以下函数模板:

template<typename RetType, typename... ArgTypes>
void foo0(std::function<RetType(ArgTypes...)> f) {}

template<typename RetType, typename ArgType>
void foo1(std::function<RetType(ArgType)> f) {}

以及以下函数:

void bar(int n) {}

为什么会出现以下情况:

 foo0<void, int>(bar);  // does not compile
 foo1<void, int>(bar);  // compiles fine

编译错误为(gcc-8 with C++17):

error: no matching function for call to 'foo0<void, int>(void (&)(int))'
   foo0<void, int>(bar);
                      ^
note: candidate: 'template<class RetType, class ... ArgTypes> void foo0(std::function<_Res(_ArgTypes ...)>)'
 void foo0(std::function<RetType(ArgTypes...)> f) {}
      ^~~~
note:   template argument deduction/substitution failed:
note:   mismatched types 'std::function<void(_ArgTypes ...)>' and 'void (*)(int)'
   foo0<void, int>(bar);

使用虚拟模板

template<typename T>
void bar(int n) {}

使 foo0<void, int>(bar<int>); 在 gcc-8 中编译正常,但在 Apple LLVM 版本 10.0.0 (clang-1000.11.45.5) 中使用 clang 时出错。

clang 错误是

error: no matching function for call to 'foo0'
  foo0<void, int>(bar<int>);
  ^~~~~~~~~~~~~~~
note: candidate template ignored: could not match 'function<void (int, type-parameter-0-1...)>' against 'void (*)(int)'
void foo0(std::function<RetType(ArgTypes...)> f) {}

Why does the following occur [?]

打电话的时候算一下

 foo0<void, int>(bar); // compilation error
 foo1<void, int>(bar); // compile

foo0()foo1() 期望 std::function 并且 bar 是指向可以转换为 std::function 但不是的函数的指针std::function.

foo1() 的情况下,您显式显示了 RetTypeArgType 模板参数,因此编译器可以将 bar 转换为 std::function<void(int)> 和所有顺利。

但是 foo0() 的情况不同,因为模板参数 ArgTypes... 是一个可变参数并且调用 foo0<void, int>(bar) 你没有显式完整的 ArgTypes... 可变参数列表但是只有第一种。

如果我没记错的话,问题是编译器试图从 bar 参数中推导出 ArgTypes... 的其余部分,但 bar 不是 std::function 所以编译器无法推导出 ArgTypes... 的其余部分,所以错误。

我想

foo0<void, int>(std::function<void(int)>{bar});

或干脆

foo0(std::function<void(int)>{bar});

或(仅限 C++17)也

foo0(std::function{bar});

应该编译,因为以这种方式调用 foo0(),函数接收一个 std::function,因此编译器可以完全推导出模板参数。

我不明白带有虚拟模板参数bar()的版本如何

foo0<void, int>(bar<int>);

可以用 g++-8 编译,我想这是一个 g++ 错误。

template<typename RetType, typename... ArgTypes>
void foo0(std::function<RetType(ArgTypes...)> f) {}

修复方法是:

template<class X>struct tag_t{using type=X;};
template<class X>using block_deduction = typename tag_t<X>::type;

template<typename RetType, typename... ArgTypes>
void foo0(block_deduction_t<std::function<RetType(ArgTypes...)>> f) {}

现在你的 foo0<void, int>(bar) 可以编译了。

一般的问题是说 foo0<void, int>,你不是在说“RetTypevoidArgTypes...int。你是说ArgTypes... 开头为 int

std::function<void(int, double)> x;
foo0<void, int>( x )

以上编译正常。

...

中的另一种方法是添加另一个重载。

留下这个:

template<typename RetType, typename... ArgTypes>
void foo2(block_deduction_t<std::function<RetType(ArgTypes...)>> f) {}

但添加:

template<typename RetType, typename... ArgTypes, class F>
void foo2(F&& f) {
  return foo2<RetType, ArgTypes...>( std::function{std::forward<F>(f)} );
}
template<int unused, class F>
void foo2(F&& f) {
  return foo2( std::function{std::forward<F>(f)} );
}

这里我们把F包装成一个构造引导的std::function

您对 foo2<int, void>( bar ) 的调用现在调用 foo2<void, int, decltype(bar)&>,这是第二次重载。然后它继续从中构造一个 std::function,只要签名完全匹配它就可以工作。