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]/3The 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