我想将可调用的(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
对象。但是当我将相同的参数传递给工厂函数时,它会抱怨。此外,当Foo
和MakeFoo
是可变参数模板时,似乎只会出现此问题。对于固定数量的模板参数,它可以正常工作。
有人可以向我解释一下吗?
答案 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)
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 ) {