C++将std :: function对象传递给variadic模板

Geo*_* P. 10 c++ templates variadic-templates c++11

我想将一个可调用的(std::function对象)传递给一个类Foo.callable指的是具有任意参数的另一个类的成员方法,因此Foo必须是可变参数模板.考虑以下代码:

struct Bar {
  void MemberFunction(int x) {}
};

template<typename ...Args>
class Foo {
 public:
  Foo(std::function<void(Bar*, Args...)> f) {}
};

int main() {
  Foo<int> m1(&Bar::MemberFunction);
  return 0;
}
Run Code Online (Sandbox Code Playgroud)

编译好了.现在我想写一个工厂函数MakeFoo()返回一个unique_ptr到一个Foo对象:

template<typename ...Args>
std::unique_ptr<Foo<Args...>> MakeFoo(std::function<void(Bar*, Args...)> f) {
  return std::make_unique<Foo<Args...>>(f);
}
Run Code Online (Sandbox Code Playgroud)

通过调用使用此功能

auto m2 = MakeFoo<int>(&Bar::MemberFunction);
Run Code Online (Sandbox Code Playgroud)

在main中,给出了以下编译器错误:

functional.cc: In function ‘int main()’:
functional.cc:21:50: error: no matching function for call to ‘MakeFoo(void (Bar::*)(int))’
       auto m2 = MakeFoo<int>(&Bar::MemberFunction);
                                                  ^
functional.cc:15:35: note: candidate: template<class ... Args> std::unique_ptr<Foo<Args ...> > MakeFoo(std::function<void(Bar*, Args ...)>)
     std::unique_ptr<Foo<Args...>> MakeFoo(std::function<void(Bar*, Args...)> f) {
                                   ^
functional.cc:15:35: note:   template argument deduction/substitution failed:
functional.cc:21:50: note:   mismatched types ‘std::function<void(Bar*, Args ...)>’ and ‘void (Bar::*)(int)’
       auto m2 = MakeFoo<int>(&Bar::MemberFunction);
Run Code Online (Sandbox Code Playgroud)

在我看来,当我调用构造函数时Foo,编译器很乐意将函数指针&Bar::MemberFunction转换为std::function对象.但是当我将相同的参数传递给工厂函数时,它会抱怨.此外,这个问题似乎只发生,当FooMakeFoo是可变参数模板.对于固定数量的模板参数,它工作正常.

有人可以向我解释一下吗?

Yak*_*ont 5

为什么它不没有明确的工作<int>

在C++ 17之前,模板类型推导是纯模式匹配.

std::function<void(Foo*)>可以存储类型的成员函数指针void(Foo::*)(),但是a void(Foo::*)()不是std::function任何类型.

MakeFoo采用它的论证和模式匹配std::function<void(Bar*, Args...)>.由于它的参数不是a std::function,因此这种模式匹配失败.

在你的另一个案例中,你已经修复了Args...,它所要做的只是转换为a std::function<void(Bar*, Args...)>.而且没有问题.

可转换的内容与可推导的内容不同.可以将有无数类型的std::function给定成员函数转换为.例如:

struct Foo {
  void set( double );
};
std::function< void(Foo*, int) > hello = &Foo::set;
std::function< void(Foo*, double) > or_this = &Foo::set;
std::function< void(Foo*, char) > why_not_this = &Foo::set;
Run Code Online (Sandbox Code Playgroud)

在这种情况下,存在歧义; 在一般情况下,可以用来从参数构造一些任意模板类型的模板参数集需要反转图灵完备计算,这涉及解决Halt.

现在,C++ 17增加了演绎指南.他们允许:

std::function f = &Foo::set;
Run Code Online (Sandbox Code Playgroud)

f为您推断签名.

在C++ 17中,演绎不指导不要在这里开始; 他们可能在其他地方或以后.

为什么它不能用于显式<int>

因为它仍然试图模式匹配并确定剩下Args...是什么.

如果您更改MakeFoo

template<class T>
std::unique_ptr<Foo<T>> MakeFoo(std::function<void(Bar*, T)> f) {
  return std::make_unique<Foo<T>>(f);
}
Run Code Online (Sandbox Code Playgroud)

突然你的代码编译.你通过它int,没有扣除,你赢了.

但是当你有

template<class...Args>
std::unique_ptr<Foo<Args...>> MakeFoo(std::function<void(Bar*, Args...)> f) {
  return std::make_unique<Foo<T>>(f);
}
Run Code Online (Sandbox Code Playgroud)

编译器看到<int>并说"好吧,所以Args...从一开始int.接下来会发生什么?".

它试图模式匹配.

它失败了.

你怎么解决它?

template<class T>struct tag_t{using type=T; constexpr tag_t(){}};
template<class T>using block_deduction=typename tag_t<T>::type;

template<class...Args>
std::unique_ptr<Foo<Args...>> MakeFoo(
  block_deduction<std::function<void(Bar*, Args...)>> f
) {
  return std::make_unique<Foo<T>>(f);
}
Run Code Online (Sandbox Code Playgroud)

现在我告诉编译器不要使用第一个参数推断.

没有什么可以演绎的,它是满足的,Args...只是int,... 它现在有效.