Suppose I have the following two classes:
template<typename T>
struct Base
{
void foo();
};
struct Derived : Base<Derived> {};
I can do this:
void (Derived::*thing)() = &Derived::foo;
And the compiler is happy (as I would expect).
When I put this in two levels of templates suddenly it explodes:
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
}
This fails with:
non-type template argument of type 'void (Base<Derived>::*)()' cannot be converted to a value of type 'void (Derived::*)()'
godbolt
Why does the simple case work and the more complicated one fail? I believe this is related to this question but am not entirely sure....
@YSC nailed the type of &Derived::foo;
. Since you are wondering why this implicit conversion...
void (Derived::*thing)() = &Derived::foo;
... flies normally but not in the template, the reason is as follows:
[temp.arg.nontype]
2 A template-argument for a non-type template-parameter shall be a converted constant expression of the type of the template-parameter.
[expr.const]
4 A converted constant expression of type T is an expression, implicitly converted to type T, where the converted expression is a constant expression and the implicit conversion sequence contains only
- [...]
The list I omitted does not contain pointer to member conversions. Thus making that template argument invalid for the parameter you specify.
A trivial fix would be to use decltype(&T::foo)
instead of void (T::*)()
as the type argument. This is a well-formed replacement:
bar<decltype(&T::foo), &T::foo>{};
Whether or not is acceptable, is of course dependent on your use case, beyond the scope of the MCVE.
That's because &Derived::foo
is in fact of type void (Base<Derived>::*)()
:
[expr.unary]/3
The result of the unary & operator is a pointer to its operand. The operand shall be an lvalue or a qualified-id. If the operand is a qualified-id naming a non-static or variant member m of some class C with type T, the result has type “pointer to member of class C of type T” and is a prvalue designating C::m.
Note the "member m of some class C with type T"... Terrible wording.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With