c++templatestemplate-inheritance

How to overload a method of base class passed as a parameter to a template class in C++?


There are two classes:

class A {
public:
    virtual void foo( int bar );
}
class B {
    virtual void foo( string bar, int baz);
}

Now, the class(es) I'm building can derive from either class. But there's some common helper code, so I want to factor it out into a base class. This common code must be called from foo and should take same arguments as the corresponding foo method. So I declare this template class, but don't know, whether it is possible to "extract" foo's signature from the template argument (which is a base class -- either A or B):

template<class Base>
class CommonBase : public Base {
    public:
        // how do I overload Base::foo here?
        void foo(/*Base::foo arguments here*/) {
            commonCode(/*Base::foo arguments here*/);
        }
    protected:
        // how do I define commonCode with Base::foo signature below?
        void commonCode(/*Base::foo arguments here*/) { ... }
}

I have little experience with C++ templates, so wondering -- is it even possible? One solution I see is to add another template parameter for method signature and pass it explicitly when specializing. But it feels redundant as the knowledge of foo signature will be already contained in the Base class parameter (and compilation should fail if Base does not provide foo at all).


Solution

  • One solution I see is to add another template parameter for method signature and pass it explicitly when specializing.

    This is on the right track, but you don't have to pass it explicitly; you can extract the type from the base class:

    template<class Base, class... Arg>
    class CommonBaseImpl : public Base {
        public:
            // how do I overload Base::foo here?
            void foo(Arg... arg) override {
                commonCode(std::forward<Arg>(arg)...);
            }
        protected:
            // how do I define commonCode with Base::foo signature below?
            void commonCode(Arg... arg) { ... }
    };
    
    template <class Base, class Foo = decltype(&Base::foo)>
    struct BaseSelector;
    
    template <class Base, class... Arg>
    struct BaseSelector<Base, void (Base::*)(Arg...)>
    {
      using type = CommonBaseImpl<Base, Arg...>;
    };
    
    template <class Base>
    using CommonBase = typename BaseSelector<Base>::type;
    

    [Live example]

    This works by using class template partial specialisation to decompose the function type. The template parameter Foo of BaseSelector will hold the type of member pointer to foo. To get this type, we use decltype(&Base::foo), the default argument for that parameter.

    However, we need to access the individual argument types from within that type. This is normally done using template partial specialisation, as here. Basically, the primary template says: "This class template takes two types, Base and Foo." They're types and we know nothing more about them. We also don't use them for anything (the primary template is not even defined).

    Then, we provide a specialisation. That effectively says: "When the type Foo happens to be a pointer to member function of Base which returns void and takes arguments of type Arg..., then do this: { partially specialised class definition }". In practice, it's just a way to assign names to the various components of the pointer-to-member type.