'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;
}
  • FooLegacyApiContractis 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. Especially T is actually a container of T most methods of FooLegacyApiContract.
  • 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.
  • (3) and (4) are such very specific implementations for T=long, U=long and T=string, U=long. They're valid only for FooImpl and its subclasses.
  • (5) is a quirk. Why do I have to put this in to gain access to non-overwritten doFoo in HalvingFirstIntFooImpl? 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) in FooImpl?
  • (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 of FooImpl.
      • In bundled, interdepedent implemementations of my own I'd might demand a FooImpl& or FooImpl*.

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