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