考虑一个带有静态方法模板的结构,它接受指向成员的函数。请注意,当方法的一个参数是实际的指向成员的函数时,无论另一个参数是否为nullptr
,都可以推导出两个模板参数。
请参阅以下代码中的问题:
struct Checker
{
template <typename T, typename V>
static void Check(
V(T::*getter)(),
void(T::*setter)(V)
);
template <typename T, typename V>
static void CheckDefault(
V(T::*getter)() = nullptr,
void(T::*setter)(V) = nullptr
);
};
struct Test
{
int Value();
void Value(int);
int Getter();
void Setter(int);
};
Checker::CheckDefault(&Test::Value); //1
Checker::CheckDefault(&Test::Value, nullptr); //2
Checker::Check(&Test::Value, nullptr); //3
Checker::CheckDefault(&Test::Getter); //4
Checker::CheckDefault(&Test::Getter, nullptr); //5
Checker::Check(&Test::Getter, nullptr); //6
&Test::Value
的正确过载可以在1中确定,而不是在2和3中确定?我期望能够调用这些方法,并将两个参数中的至少一个设置为实际的指向成员的函数,从而使得演绎成功。像这样:
Checker::Check(&Test::Value, &Test::Value); // Both getter and setter
Checker::Check(&Test::Value, nullptr); // Only getter
Checker::Check(nullptr, &Test::Value); // Only setter
@Oliv在例外答案中的讨论解释了为什么它不能按我的预期发挥作用,这使我指出了解决我的具体问题的正确方向。
正如@Ben Voigt建议的那样,我最终使用了货运代理。就像是:
template <typename T, typename V>
using GetterT = V(T::*)();
template <typename T, typename V>
using SetterT = void(T::*)(V);
template <typename T, typename V>
void Checker::CheckGetterAndSetter(
GetterT<T, V> getter,
SetterT<T, V> setter
)
{
// Do stuff with getter and setter.
}
template <typename T, typename V>
void Checker::CheckGetter(
GetterT<T, V> getter
)
{
SetterT<T, V> null = nullptr;
return CheckGetterAndSetter(getter, null);
}
template <typename T, typename V>
void Checker::CheckSetter(
SetterT<T, V> setter
)
{
GetterT<T, V> null = nullptr;
return CheckGetterAndSetter(null, setter);
}
TL; DR它成功,因为默认参数不用于模板参数推导。在其他情况下(2,3,5,6)它失败了因为nullptr_t
没有T(U::*)(V)
(甚至不是T*
)的形式。
我们来看一个更简单的例子:
template<class T>
void f(T,T*=nullptr);
int main(){
f(10,nullptr);// (1) error
f(10);// (2) OK
}
对于(1)编译器认为它应该能够从第一个参数和第二个参数推导出T
,因为形式为T
或T*
的参数是免赔额。由于nullptr_t
不是T*
的形状,编译失败。
对于(2),编译器仅对函数的第一个参数执行模板参数推导,因为默认参数不在模板参数推导中播放。所以它毫不含糊地推断T
是int
。然后当调用该函数时,nullptr
将转换为int*
。