c++gcclanguage-lawyerusingusing-directives

GCC error when using parent class method as derived class method


I have a function in my code which only accepts a class member method as a template parameter. I need to call this method using a class method which is inherited from a parent class. Here is an example code of my problem:

template <class C>
class Test {
public:
    template<typename R, R( C::* TMethod )()> // only a member function should be accepted here
    void test() {} 
};

class A {
    public:
    int a() { return 0; } // dummy method for inheritance
};

class B : public A {
public:
    using A::a; // A::a should be declared in the B class declaration region

    // int a() { return A::a(); } // if this lines is activated compliation works
};

int main() {
    auto t = Test<B>();

    t.test<int, &B::a>();
}

With the MSVC 2019 compiler the code compiles without problems. However the gcc produces following error:

<source>: In function 'int main()':
<source>:23:23: error: no matching function for call to 'Test<B>::test<int, &A::a>()'
   23 |     t.test<int, &B::a>();
      |     ~~~~~~~~~~~~~~~~~~^~
<source>:5:10: note: candidate: 'template<class R, R (B::* TMethod)()> void Test<C>::test() [with R (C::* TMethod)() = R; C = B]'
    5 |     void test() {}
      |          ^~~~
<source>:5:10: note:   template argument deduction/substitution failed:
<source>:23:17: error: could not convert template argument '&A::a' from 'int (A::*)()' to 'int (B::*)()'
   23 |     t.test<int, &B::a>();
      |    

As far as I understand the gcc is still handling the type of B::a as A::a. On the cpp reference its saying that using

Introduces a name that is defined elsewhere into the declarative region where this using-declaration appears.

So in my opinion the using should transfer the A::a method to the declerativ region of B and therefor it should be handled as B::a. Am I wrong or is there a bug in GCC?

Here is the example on Compiler Explorer: https://godbolt.org/z/TTrd189sW


Solution

  • There is namespace.udecl, item 12 (emphasis mine):

    For the purpose of forming a set of candidates during overload resolution, the functions named by a using-declaration in a derived class are treated as though they were direct members of the derived class. [...] This has no effect on the type of the function, and in all other respects the function remains part of the base class.

    Thus, a is not a member of B, and the type of &B::a is int (A::*)().
    (&B::a means the same thing regardless of whether you include using A::a; or not)

    There is no point to using named functions from a base class except to work around the "hiding problem" when you want to overload or override them.