C ++将std :: function对象传递给variadic模板
我想将可调用( std::function
对象)传递给Foo
类。 可调用是指另一个具有任意参数的类的成员方法,因此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);
在主,给我以下编译器错误:
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
是可变模板的时候。 对于固定数量的模板参数,它工作正常。
有人可以向我解释这个吗?
为什么没有显式的<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;
在这种情况下,存在不明确性; 在一般情况下,可以用来从参数构造一些任意模板类型的模板参数集需要颠倒turing-complete计算,这涉及到解决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>
并说“好的,所以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);
}
现在我已经告诉编译器不要推断使用第一个参数。
没有什么可推论的,它满足于Args...
就是int
,现在它可以工作。
编译器无法从不同类型(例如成员函数指针)中推导std::function
的模板参数。 即使可以从该类型的对象构造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);
}
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)
国际海事组织,最正确的修复(给定所需的用法)是使参数不被推断:
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 ) {
链接地址: http://www.djcxy.com/p/66899.html