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

时间:2017-09-26 15:30:55

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

我想将可调用的(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;
}

编译好。现在我想编写一个工厂函数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);
}

通过调用

使用此功能
auto m2 = MakeFoo<int>(&Bar::MemberFunction);
在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);

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

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

3 个答案:

答案 0 :(得分:5)

为什么没有明确的<int>

就行不通

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

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

MakeFoo获取其参数,模式匹配std::function<void(Bar*, Args...)>。由于其参数不是std::function,因此此模式匹配失败。

在您的其他情况下,您修复了Args...,而且所有必须做的就是转换为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;

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

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

std::function f = &Foo::set;

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

突然你的代码编译了。你传递它int,没有扣除,你赢了。

但是当你有

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

编译器看到<int>并说'#34; ok,所以Args...int开头。接下来会发生什么?&#34;。

它试图模式匹配。

它失败了。

你怎么解决它?

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

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

无需推断,我们认为Args...只是int和...... it now works

答案 1 :(得分:2)

编译器不能从其他类型推导出std::function的模板参数,例如成员函数指针。即使可以从该类型的对象构造std::function,要考虑构造函数,必须首先知道std::function的模板参数。

为了帮助推断,请添加另一个重载:

template<typename ...Args>
std::unique_ptr<Foo<Args...>> MakeFoo(void(Bar::*f)(Args...)) {
  return std::make_unique<Foo<Args...>>(f);
}

答案 2 :(得分:1)

MakeFoo<int>( whatever );

相当于调用假设的

template<typename ...Tail>
std::unique_ptr<Foo<int,Tail...>> MakeFoo( std::function<void(Bar*,int,Tail...)> f) {
  return std::make_unique<Foo<int,Tail...>>(f);
}

显然,编译器在给定void(Bar::*)(int)

的情况下决不会推断出Tail为空

IMO,最正确的修复(给定所需用法)是使args不推断:

template< typename T >
struct nondeduced { using type = T; };

template<typename ...Args>
std::unique_ptr<Foo<Args...>> MakeFoo( std::function<void(Bar*, typename nondeduced<Args>::type... )> f ) {