假设我有以下两个类:
template<typename T>
struct Base
{
void foo();
};
struct Derived : Base<Derived> {};
我可以做这个:
void (Derived::*thing)() = &Derived::foo;
编译器很高兴(正如我所料)。
当我把它放在两个级别的模板中时突然爆炸:
template<typename T, T thing>
struct bar {};
template<typename T>
void foo()
{
bar<void (T::*)(),&T::foo>{};
}
int main()
{
foo<Derived>(); // ERROR
foo<Base<Derived>>(); // Works fine
}
这失败了:
non-type template argument of type 'void (Base<Derived>::*)()' cannot be converted to a value of type 'void (Derived::*)()'
为什么简单的案例工作而更复杂的案例失败了?我相信这与this问题有关,但我不完全确定....
@YSC钉牢了&Derived::foo;
的类型。既然你想知道为什么这个隐式转换......
void (Derived::*thing)() = &Derived::foo;
...飞行正常但不在模板中,原因如下:
[temp.arg.none type]
2非类型模板参数的模板参数应该是模板参数类型的转换常量表达式。
[Expr.const]
4 T类型的转换常量表达式是一个表达式,隐式转换为T类型,其中转换后的表达式是常量表达式,隐式转换序列仅包含
- [...]
我省略的列表不包含pointer to member conversions。因此,使该模板参数对您指定的参数无效。
一个简单的解决方法是使用decltype(&T::foo)
而不是void (T::*)()
作为类型参数。这是一个结构良好的替代品:
bar<decltype(&T::foo), &T::foo>{};
无论是否可接受,当然取决于您的用例,超出了MCVE的范围。
这是因为&Derived::foo
实际上是void (Base<Derived>::*)()
类型:
[expr.unary]/3
一元&运算符的结果是指向其操作数的指针。操作数应为左值或限定ID。如果操作数是一个qualified-id,命名一个类型为T的某个类C的非静态或变体成员m,则结果的类型为“指向类型为C的C类成员的指针”,并且是一个指定C :: m的prvalue。
注意“某些C类的成员m与T型”......可怕的措辞。
请注意,即使没有模板,这也会出错,例如,如果您的类不是模板:
struct Base {
void foo();
};
struct Derived : Base {};
你仍然不能做foo<Base>()
:https://ideone.com/MQE0ff
一个可能的替代解决方案将涉及,首先为了简化,而不是采取2个模板参数我将使bar
使用auto
模板参数类型:
template<auto thing>
struct bar{};
接下来我们需要实现is_specialization_of
:
template<template<typename...> class T, typename U>
struct is_specialization_of : std::false_type {};
template<template<typename...> class T, typename... Ts>
struct is_specialization_of<T, T<Ts...>> : std::true_type {};
现在我们可以重写foo
来使用is_specialization_of
我们可以确定我们是否通过了Base
专门化或另一个类(我们假设它来自Base
专业化。)
template<typename T>
void foo()
{
conditional_t<is_specialization_of<Base, T>::value, bar<&T::foo>, bar<&Base<T>::foo>>{};
}
我已经稍微扩展了你的例子,实际上在thing
中调用了bar
,然后另外加入了我的建议。你可以在这里查看:https://coliru.stacked-crooked.com/a/2a33b8bd38896ff5