2

I can't seem to figure out what is going on with a particular templated method.

A similar templated method has been in my codebase for a while now and has been used in an identical manner as Bar::Bar(IFoo&), the only difference being that the function being passed in is inherited.

I have an interface IFoo which defines the templated function in question.

struct IFoo {
    template<class T>
    void doSomething(bool (T::*method)(int), T *instance) {
        std::cout << (instance->*method)(5) << "\n";
    }
};

I have a class Bar which is a child of another class

struct Parent {
    virtual bool setValue(int a) { return true; }
};
struct Bar : Parent { };

When I try to use IFoo::doSomething the compiler just doesn't see the matching function

int main() {
    Bar b;
    IFoo foo;
    foo.doSomething(&Bar::setValue, &b);
}

I get the following compiler message
error: no matching function for call to 'IFoo::doSomething(bool (Parent::*)(int), Bar*)'

What I'm really surprised about is that I don't get a candidate suggestion that for the templated IFoo::doSomething function.

C++98 solutions only please.

HTNW
  • 27,182
  • 1
  • 32
  • 60
lancegerday
  • 752
  • 1
  • 8
  • 24

1 Answers1

2

As the error says, the type of Bar::setValue is actually bool (Parent::*)(int). This interferes with the template parameter deduction for doSomething because it has two Ts that need to be the same. You can help the compiler with the deduction by casting this:

int main() {
    Bar b;
    IFoo foo;
    foo.doSomething(&Bar::setValue, static_cast<Parent*>(&b));
}

Live Demo

Thanks to JVApen for pointing out that alternatively you can make doSomething more generic by allowing two different types and let Callback do the conversion:

template<class T, class U>
void doSomething(bool (T::*method)(int), U *instance);
HTNW
  • 27,182
  • 1
  • 32
  • 60
463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
  • 1
    Alternatively, one could use 2 template parameters and let Callback implicitly upcast the Bar to a Parent pointer: https://godbolt.org/z/6adT8x – JVApen Dec 29 '20 at 19:50
  • 1
    @JVApen thanks for the link. I thought the same, but admittedly was too lazy to verify that it works ;) – 463035818_is_not_an_ai Dec 29 '20 at 19:51
  • I was gonna add `static_assert(std::is_base_of_v)`, however, that didn't exist 22 years ago – JVApen Dec 29 '20 at 19:52
  • Sadly static_cast is computationally expensive and that may be an issue for me. – lancegerday Dec 29 '20 at 19:53
  • 2
    @lancegerday "static_cast is computationally expensive" this must be a misunderstanding. Why do you think so? – 463035818_is_not_an_ai Dec 29 '20 at 19:54
  • @HTNW yes, caused some confusion here, but once I understood it I agree with the edit – 463035818_is_not_an_ai Dec 29 '20 at 19:59
  • @lancegerday As you can see on https://godbolt.org/z/oGGebY, there ain't assembly linked to the static_cast (and you don't need it, see right below it) – JVApen Dec 29 '20 at 19:59
  • @lancegerday see here https://stackoverflow.com/questions/6445841/c-static-cast-runtime-overhead. Don't miss the comment under the accepted answer. `static_cast` from a derived to base pointer is almost free – 463035818_is_not_an_ai Dec 29 '20 at 20:00
  • 1
    There's also the template dark magic "deduction blocker": `template struct type_identity { /* stolen from C++20 */ typedef T type; }; template void doSomething(bool (T::*method)(int), typename type_identity::type *instance);`, in which `doSomething(&Bar::setValue, &b)` works because only `&Bar::setValue` contributes to the choice of `T = Parent`. The upcast then becomes implicit. – HTNW Dec 29 '20 at 20:06
  • Alternatively, you can cast the other argument: `static_cast(&Bar::setValue)` – Artyer Dec 29 '20 at 20:12