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

4

考虑以下函数模板:

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) {}

在gcc-8中,foo0<void, int>(bar<int>);可以编译通过,但是在使用Apple LLVM version 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) {}

尝试推断std::function的类型几乎总是错误的。你真正想要解决的问题是什么? - Barry
2个回答

2
"为什么会出现以下情况?"
请注意,当您调用时。
 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});

or (C++17 only) also

foo0(std::function{bar});

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

我不明白带有虚拟模板参数的bar()版本的含义。

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

可以使用g++-8进行编译,我认为这是一个g++的bug。最初的回答。

1
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,只要签名完全匹配即可工作。


谢谢,你有没有想法为什么我的最后一个例子在使用gcc编译时可以通过,但是在使用clang编译时却不行? - dtell

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接