Pointer to Parent Class Member Method

Viewed 103

I am trying to get the following simple code to work:

#include <functional>
#include <iostream>

class Parent {
public:
    void method(int a) {
        std::cout << "Parent::method(" << a << ")" << std::endl;
    }
};

class Child : public Parent {
public:
    
};

template <typename T>
std::function<void()> test(T* v, void(T::* fn)(int)) {
    return std::function<void()>([v, fn](){
        v->fn(10);
    });
}

int main() {
    Child c;
    
    std::function<void()> fn = test(&c, &Child::method);
    
    fn();
    
    return 1;
}

But am getting the following error:

note:   template argument deduction/substitution failed:
note:   deduced conflicting types for parameter 'T' ('Child' and 'Parent')
  std::function<void()> fn = test(&c, &Child::method);

I get the same problem if I change my pointer to Parent::method. Since Child "is a" parent, why is the compiler unable to deduce this template?

Thanks!

2 Answers

Child "is a" parent

It is in the object-oriented sense. But template argument deduction is primarily about type identity.

&Child::method names a member of Parent. It may be accessible in Child but it is declared in Parent. So the only available deduction of T from that member is Parent. On the other hand, the pointer deduces T to be Child. The identity of T here cannot be uniquely determined. So it's a deduction failure.

But we can still write your function template with a tweak:

template <class C, class P>
std::function<void()> test(C* v, void(P::* fn)(int)) {
    return std::function<void()>([v, fn](){
        (v->*fn)(10);
    });
}

Now the classes are deduced in their own context, without affecting the other. This can be enough as is (since (v->*fn)(10) will be ill-formed for unrelated classes). But additional checks may be put in-place to make the overload sfinae friendly.

The pointer &Child::method is a member function pointer to a method in Parent. The fact that Child can access this method is irrelevant as far as template argument deduction is concerned. The compiler sees that and deduces a Parent for the T parameter based on the first argument. At the same time, it deduces Child for T based on the first argument &c. This conflicts and you get an error.

You can get around this by putting the second argument of the function template in a non-deduced context

template <typename T>
struct I { using type = T; };

template <typename T>
std::function<void()> test(T* v, void(I<T>::type::* fn)(int)) {
                                   // ^________^  T here is not deduced
    return std::function<void()>([v, fn](){
        (v->*fn)(10);
    });
}

Now template argument deduction is unaffected, but you can still call method since it's accessible from the Child class.

Related