Search code examples
c++templatesinterfacetemplate-specializationtype-traits

How to limit template parameters to a descendent that is a specialization of a templated interface?


Assume the following situation:

  1. There is a templated interface defining a set of operations on different data types.
  2. This interface is implemented by various specialized classes defining the operations for actual data types.
  3. There is some managing class that has to work instances of classes as defined in 2.

Simplified example code could look like this:

#include <iostream>
#include <type_traits>

template <typename R, typename S>
class ICanDoIt
{
    public:
        virtual void doStuff() = 0;

    protected:
        ICanDoIt<R, S>(R rA, S sA) : r(rA), s(sA) {};
        R r;
        S s;
};

class DoesIt : public ICanDoIt<int, double>
{
        public:
            DoesIt(int iA, double dA) : ICanDoIt(iA, dA) {};

            virtual void doStuff()
                { std::cout << "r * s = " << r * s << " done." << std::endl; }
};

template <typename T>
class NeedsSomeoneWhoCanDoIt
{
    static_assert(std::is_base_of<ICanDoIt<R, S>, T>::value, 
                  "T needs to be able to do it.");

    public:
        NeedsSomeoneWhoCanDoIt(const T& doesItA) : doesIt(doesItA) {};
        void getItDone() { doesIt.doStuff(); };

    private:
        T doesIt;
};


int main()
{
    DoesIt doesIt(5, 2.2);
    NeedsSomeoneWhoCanDoIt<DoesIt> needsIt(doesIt);
    needsIt.getItDone();
}

If you untemplate the interface "ICanDoIt" the code will actually work. But the static_assert for the templated version will fail because ICanDoIt's template arguments are wrapped and hidden by the specialization performed in the decalaration of DoesIt.

How can I limit the managing classes (NeedsSomeoneWhoCanDoIt) template parameter "T" to any specialization of ICanDoIt, regardless of which type has been chosen for R, S during the specialization of ICanDoIt?


Solution

  • You could always make the actual types for R and S used to instantiate ICanDoIt accessible to a derived class, i.e.

    template <typename R, typename S> class ICanDoIt {
    public:
      typedef R R_t;
      typedef S S_t;
    
      virtual void doStuff() = 0;
    };
    

    so that your static_assert would become

    static_assert(std::is_base_of<ICanDoIt<typename T::R_t, typename T::S_t>,
                                  T>::value,
                  "T needs to be able to do it.");
    

    Depending on what your actual code looks like the design might become clearer if you'd define a purely abstract base class (i.e. an actual type ICanDoItBase instead of a template) from which you'd inherit the currently templated functionality in ICanDoIt which would again be a base of DoesIt.

    NeedsSomeoneWhoCanDoIt could then directly use the the polymorphic base class ICanDoItBase without any needs for additional type checks.