2

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).

peetonn
  • 2,942
  • 4
  • 32
  • 49
  • A function template does not seem like the right tool for this job. If the common code uses members common to `A` and `B`, then I suggest you create a base class for `A` and `B`, which contains the common code and common members. – Beta Jun 24 '19 at 18:49
  • I have no access to `A` and `B`. They are meant to be derived from. – peetonn Jun 24 '19 at 18:53

1 Answers1

2

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.

Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
  • thanks! "you can extract the type from the base class" -- this is what I was not sure how to do. this is helpful! could you please, maybe give some pointers to understand the mechanics of what's happening here? For instance, why partial specialization of `BaseSelector` is needed? – peetonn Jun 24 '19 at 19:37
  • that's interesting... thank you a lot! i'm wondering, is there a way to `typedef` `void(Arg...)` in `CommonBaseImpl`? – peetonn Jun 24 '19 at 20:05
  • 1
    @peetonn Of course. `using BaseFooType = void(Arg...);`, or with `typedef` notation, `typedef void BaseFooType(Arg...);`. – Angew is no longer proud of SO Jun 24 '19 at 20:07