Question

Say you have two structures, Generic_A and Generic_B. Generic_B is derived from Generic_A. Why is it that when Generic_B tries to access a method in its parent, Generic_A, it generates the following error:

test2.cpp: In function 'int main()':
test2.cpp:26: error: no matching function for call to 'case1(void (Generic_A::*)()' 

This code, compiled using gcc version 4.4.6, replicates the problem:

#include <stdio.h>

struct Generic_A
{
    void p1() { printf("%s\n", __PRETTY_FUNCTION__); };
};

struct Generic_B : public Generic_A
{
    void p2() { printf("%s\n", __PRETTY_FUNCTION__); };
};

template <class T,class... ARGS>
void case1( void (T::*p)(ARGS...) ) {
    printf("%s\n", __PRETTY_FUNCTION__);
}

template <class T>
void case2( void (T::*p)() ) {
    printf("%s\n", __PRETTY_FUNCTION__);
}

main()
{
  //generates error
    case1<Generic_B>(&Generic_B::p1);

  //compiles fine
    case2<Generic_B>(&Generic_B::p1);
}

The only apparent difference between the two function calls is that case1() has a template argument parameter, and case2() doesn't. Shouldn't they both allow you to pass a function pointer to a method in Generic_B's parent (ie &Generic_B::p1)?

Also, casting the function pointer in case1 seems to sometimes resolve the error:

case1<Generic_B>( (void (Generic_B::*)()) &Generic_B::p1);

What is going on?

Was it helpful?

Solution

This is tricky, but it turns out g++ is correct.

First, the type of expression &Generic_B::p1 is void (Generic_A::*)(). The compiler uses Generic_B:: to qualify its name lookup and finds the member of Generic_A. The expression type depends on the definition of the member found, not the type used within the qualified-id.

But it's also legal to have

void (Generic_B::*member)() = &Generic_B::p1;

since there is an implicit conversion from void (Generic_A::*)() to void (Generic_B::*)().

Whenever a function template is used as a function call, the compiler goes through three basic steps (or attempts to):

  1. Substitute any explicit template arguments for the template parameters in the function declaration.

  2. For each function parameter that still involves at least one template parameter, compare the corresponding function argument to that function parameter, to (possibly) deduce those template parameters.

  3. Substitute the deduced template parameters into the function declaration.

In this case, we have the function template declaration

template <class T,class... ARGS>
void case1( void (T::*p)(ARGS...) );

where the template parameters are T and ARGS, and the function call expression

case1<Generic_B>(&Generic_B::p1)

where the explicit template argument is Generic_B and the function argument is &Generic_B::p1.

So step 1, substitute the explicit template argument:

void case1( void (Generic_B::*p)(ARGS...) );

Step 2, compare parameter types and argument types:

The parameter type (P in Standard section 14.8.2) is void (Generic_B::*)(ARGS...). The argument type (A) is void (Generic_A::*)().

C++ Standard (N3485) 14.8.2.1p4:

In general, the deduction process attempts to find template argument values that will make the deduced A identical to A (after the type A is transformed as described above). However, there are three cases that allow a difference:

  • If the original P is a reference type, the deduced A (i.e., the type referred to by the reference) can be more cv-qualified than the transformed A.

  • The transformed A can be another pointer or pointer to member type that can be converted to the deduced A via a qualification conversion (4.4).

  • If P is a class and P has the form simple-template-id, then the transformed A can be a derived class of the deduced A. Likewise, if P is a pointer to a class of the form simple-template-id, the transformed A can be a pointer to a derived class pointed to by the deduced A.

So type deduction allows for certain implicit conversions involving const / volatile and/or derived-to-base conversions, but implicit conversions of pointers to members are not considered.

In the case1 example, type deduction fails, and the function is not a match.

Unfortunately, there's no way to explicitly specify that your template parameter pack ARGS should be substituted with an empty list. As you already discovered, you can get this working by explicitly doing the necessary pointer to member function conversion yourself, even though it's otherwise valid as an implicit conversion.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top