我正在做一些看起来像这样的包装器:
#include <iostream>
template<class T, class Value>
void Apply(void (T::*cb)(Value), T* obj, Value v)
{
(obj->*cb)(v);
}
class Foo
{
public:
void MyFunc(const int& i)
{
std::cout << i << std::endl;
}
const int& GetValue()
{
return i_;
}
private:
int i_ = 14;
};
int main()
{
Foo f;
Apply(&Foo::MyFunc, &f, f.GetValue());
}
我收到了这个错误:
Apply
:找不到匹配的重载函数。 void Apply(void (__thiscall T::* )(Value),T *,Value)
:模板参数Value
不明确,可以是int
或const int &
。 void Apply(void (__thiscall T::* )(Value),T *,Value)
:无法从Value
推断const int
的模板参数。 所以我得到它来自模板参数演绎,但我不明白如何。为什么Value
不两次评估为const int&
?
答案 0 :(得分:13)
目前,模板参数Value
在调用Apply
的两个不同位置推导出:从指向成员函数参数的指针和最后一个参数。从&Foo::MyFunc
开始,Value
被推断为int const&
。从f.GetValue()
开始,Value
被推断为int
。这是因为为了模板推断而删除了引用和顶级cv限定符。由于参数Value
的这两个推论不同,因此推论失败 - 从重载集中删除Apply()
,因此我们没有可行的重载。
问题是Value
是在两个不同的地方推断出来的,所以让我们来防止这种情况发生。一种方法是在非推导的上下文中包含其中一个用途:
template <class T> struct non_deduced { using type = T; };
template <class T> using non_deduced_t = typename non_deduced<T>::type;
template<class T, class Value>
void Apply(void (T::*cb)(Value), T* obj, non_deduced_t<Value> v)
{
(obj->*cb)(v);
}
最后一个参数v
的类型为non_deduced_t<Value>
,顾名思义,它是一个非推断的上下文。因此,在模板推导过程中,Value
从指向成员函数的指针推导为int const&
(如前所述),现在我们只需将其插入到v
的类型中。
或者,您可以选择将cb
推断为自己的模板参数。此时Apply()
只会减少到std::invoke()
。
答案 1 :(得分:3)
表达式f.GetValue()
是const int
类型的左值。当按值传递时,模板参数推导会推导出类型int
。一般情况下,从Value
推断Value v
永远不会生成具有顶级cv资格的参考或类型。
您可能希望有两个单独的模板参数来代替Value
(一个用于函数类型&#39; s参数,一个用于实际参数类型)并使用SFINAE来禁用{{ 1}}当Apply
无法使用cb
(或v
进行硬错误调用)时。{/ p>