'Use templates to implement a subset of multiple virtual methods of a templated class
I am working on incorporating a new implementation based on an older, fixed API and moving in somewhat contradicting terrain because I need to pair a templated pure interface with runtime-dependent usage. While doing so I want to be as lazy as possible and reduce boilerplate code to a minimum. I've got a working version with some minor quirks and a lot of boilerplate needed. Consider the following code example.
template<typename T, typename U>
class FooLegacyApiContract {
public:
// FooLegacyApiContract must not hold any default implementation and _only_ defines an unchangeable contract
virtual void doFoo(const T &x, const U &y) = 0;
// about 20 other methods, some with T and/or U, some without.
};
class FooImpl
: public FooLegacyApiContract<int, int>, public FooLegacyApiContract<double, int>, public FooLegacyApiContract<long,long>, public FooLegacyApiContract<std::string,long> {
public:
// (1) implementation of doFoo(int, int) -> delegate to common solution, here in FooImpl. Still virtual.
virtual void doFoo(const int &x, const int &y) override {
doIntegerLikeStuff(x, y);
}
// (2) implementation of doFoo(long,long) -> delegate to common solution, here in FooImpl. Still virtual.
virtual void doFoo(const long &x, const long &y) override {
doIntegerLikeStuff(x, y);
}
// (3) implementation of doFoo(double,int) -> do special stuff, just here in FooImpl
virtual void doFoo(const double &x, const int &y) override {
std::cout << "FooImpl Doing specific stuff with a double,int=" << x << "," << y << std::endl;
}
// (4) implementation of doFoo(std::string, long) -> do special stuff, just here in FooImpl
virtual void doFoo(const std::string &x, const long &y) override {
std::cout << "FooImpl Doing specific stuff with a string,long=" << x << "," << y << std::endl;
}
private:
template<typename T, typename U>
void doIntegerLikeStuff(const T &x, const U &y) {
// TODO: maybe statically assert that T,U has a valid combination supported by this method here
std::cout << "FooImpl Doing stuff with a either <int,int> or <long, long>=" << x << "," << y << std::endl;
}
};
// Reuse FooImpl but change a method
class HalvingFirstIntFooImpl : public FooImpl {
public:
using FooImpl::doFoo; // (5) Without this the other doFoo will not be visible.
void doFoo(const int &x, const int &y) override {
std::cout << "HalvingFirstIntFooImpl Doing stuff differently with an int,int=" << x / 2 << "," << y << std::endl;
}
};
// completely different, scoped implementation of contract, allows only ints
class DoublingFirstIntImpl : public FooLegacyApiContract<int, int> {
public:
void doFoo(const int &x, const int &y) override {
std::cout << "DoublingFirstIntImpl int=" << x * 2 << std::endl;
}
};
int main() {
FooImpl fi;
HalvingFirstIntFooImpl halve_ints_fi;
DoublingFirstIntImpl doubling_ints_only;
fi.doFoo(1, 1);
fi.doFoo(2L, 2L);
fi.doFoo(3.1415, 35);
fi.doFoo("test", 42L);
// the following line does not work without (5)
halve_ints_fi.doFoo("more testing", 12345L);
FooImpl& fi_ref = halve_ints_fi;
fi_ref.doFoo(22, 22);
fi_ref.doFoo(1337L, 4711L);
fi_ref.doFoo("another test", 50L);
// Dynamic dispatch needed b/c user input chooses betwen FooImpl, HalvingFirstIntFooImpl and DoublingFirstIntImpl
FooLegacyApiContract < int, int > *ref = &fi; // (6) Why is this not working with FooLegacyApiContract<int>& ?
ref->doFoo(200, 200);
ref = &halve_ints_fi;
ref->doFoo(300, 300);
ref = &doubling_ints_only;
ref->doFoo(400, 400);,
FooLegacyApiContract < double , int > * another_ref = &fi;
another_ref = &halve_ints_fi;
// the following line does not compile, which is good!
// another_ref = &doubling_ints_only;
}
FooLegacyApiContract
is the contract class I'm not allowed to change, as it's library code outside of my responsibility. It rarely changes and can be considered fixed. The version chosen here is obv. very contrived. In reality it's a repository-API with a lot of different demanded semantics.T
,U
are not pritive. EspeciallyT
is actually a container ofT
most methods ofFooLegacyApiContract
.- The long definition of
FooImpl
is fine. I guess I could reduce it with with some CRTP variadic template magic. - Ideally I want (1) and (2) to be generated by the compiler.
- In a perfect world I'd speficy the exceptions to the rule of T,U-pairs for which a non-generic implementation needs to be supplied manually.
- Even more perfect would be some form of wildcard for T and/or U.
- Subclasses of FooImpl need to be able to change this behavior.
HalvingFirstIntFooImpl
is an example. Must(?) remain virtual so that dynamic dispatch works in the Impl class hierarchy.
- In a perfect world I'd speficy the exceptions to the rule of T,U-pairs for which a non-generic implementation needs to be supplied manually.
- (3) and (4) are such very specific implementations for
T=long, U=long
andT=string, U=long
. They're valid only forFooImpl
and its subclasses. - (5) is a quirk. Why do I have to put this in to gain access to non-overwritten
doFoo
inHalvingFirstIntFooImpl
? I guess this is because of doFoo coming from a template.- Is there a short way to tell the compiler to auto-use any
FooLegacyApiContract
method implemented (automatically or manually) inFooImpl
?
- Is there a short way to tell the compiler to auto-use any
- (6) is finally the use case I need to support.
- At runtime, user input defines what implementation will be used by assigning a pointer, but preferribly a reference.
- Using a reference here does not work due to slicing issues. I solved it with using a pointer for now, but I'd like a reference.
- The type of that pointer/reference depends on the context.
- Legacy code uses
FooLegacyApiContract<T,U>&
and I'd like to pass in instances ofFooImpl
. - In bundled, interdepedent implemementations of my own I'd might demand a
FooImpl&
orFooImpl*
.
- Legacy code uses
I feel like going CRTP and partial template specialization, maybe even SFINAE could be parts of a solution but a have a hard time mixing it with the requirement to not touch FooLegacyApiContract
and keep its virtual
-nature.
I'd be really happy if someone could point me in the right direction.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|