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_ptrFoo对象:

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对象。 但是,当我将相同的参数传递给工厂功能时,它会抱怨。 而且,这个问题似乎只发生在FooMakeFoo是可变模板的时候。 对于固定数量的模板参数,它工作正常。

有人可以向我解释这个吗?


为什么没有显式的<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

上一篇: C++ Passing std::function object to variadic template

下一篇: Pointers to member as variadic template parameters