Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Converting member function pointers in templates

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....

like image 450
DarthRubik Avatar asked Sep 13 '18 13:09

DarthRubik


2 Answers

@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.

like image 155
StoryTeller - Unslander Monica Avatar answered Sep 19 '22 10:09

StoryTeller - Unslander Monica


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.

like image 32
YSC Avatar answered Sep 20 '22 10:09

YSC