我正在做一些包装,看起来像这样:
#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)
:无法从const int
中推断出Value
的模板参数。
所以我得到它,它来自模板参数演绎,但我不明白如何。为什么Value
不两次都求值为const int&
?
为什么失败
目前,模板形参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()
。
表达式f.GetValue()
是类型为const int
的左值。当它通过值传递时,模板参数推导会推导类型int
。一般来说,从Value v
推导Value
, 将永远不会产生具有顶级cv-限定符的引用或类型。
你可能想用两个单独的模板参数来代替Value
(一个用于函数类型的参数,一个用于实际参数类型),当cb
不能被v
调用时,使用SFINAE来禁用Apply
(或static_assert
用于硬错误)。