Reputation: 3985
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::*)()'
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....
Upvotes: 6
Views: 159
Reputation: 170259
@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.
Upvotes: 6
Reputation: 38969
Note that this is going to be an error even without templates, for example if your classes were not templates:
struct Base {
void foo();
};
struct Derived : Base {};
You still can't do foo<Base>()
: https://ideone.com/MQE0ff
A possible alternative solution would involve, first for simplification, rather than taking 2 template parameters I'm going to have bar
use the auto
template parameter type:
template<auto thing>
struct bar{};
Next we'd need to implement 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 {};
Now we can rewrite foo
to use is_specialization_of
we can determine if we're passed a Base
specialization or another class (which we presume derives from a Base
specialization.)
template<typename T>
void foo()
{
conditional_t<is_specialization_of<Base, T>::value, bar<&T::foo>, bar<&Base<T>::foo>>{};
}
I've expanded your example slightly to actually call thing
in bar
and just otherwise incorporated my suggestions. You can check that out here: https://coliru.stacked-crooked.com/a/2a33b8bd38896ff5
Upvotes: 0
Reputation: 40160
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.
Upvotes: 4