Search code examples
c++c++11abstraction

Abstraction over single c++ object and std::pair of objects using templates


Assume the following template construction:

enum class ENUM {SINGLE, PAIR};
// General data type
template<ENUM T, class U>class Data;
// Partially specialized for single objects
template<class U>Data<ENUM::SINGLE, U> : public U {
  // Forward Constructors, ...
};
// Partially specialized for pairs of objects
template<class U>Data<ENUM::PAIR, U> : public std::pair<U,U> {
  // Forward Constructors, ...
};

In my code I want to be able to write something like

template<ENUM T>someMethod(Data<T, SomeClass> data) {
  for_single_or_pair {
    /*
     * Use data as if it would be of type SomeClass
     */
  }
}

which should do the same as the combination of the following methods:

template<>someMethod(Data<ENUM::SINGLE, SomeClass> data) {
  data.doStuff();
}

template<>incrementData(Data<ENUM::PAIR, SomeClass> data) {
  data.first.doStuff();
  data.second.doStuff();
}

I.e. I want to be able to use a pair of objects (of the same type) as if it would be a single object. Of course I could reimplement the methods of a type T for Data<ENUM::PAIR, T> (see the answer of dau_sama) which for the given example would look like:

template<>Data<ENUM::PAIR, SomeClass> : public std::pair<SomeClass, SomeClass> {
  doStuff() {
    this->first.doStuff();
    this->second.doStuff();
  }
};

But I would have to do this for many methods and operators and many different types, although the methods and operators would all look like this example.

The syntax of the solution may be very different from what I wrote above, this is just to demonstrate what I want to achieve. I would prefer a solution without macros, but could also live with that.

Can such an abstraction be realized in C++11?

The reasons I want to do this are

  1. I do not have to specialize templated methods that shall work for ENUM::Single and ENUM::PAIR when all differences between the specializations would math the pattern above (avoid a lot of code duplication).
  2. The same pattern is occuring very often in my code and I could avoid implementing workarounds in many places, which would be almost identical in each case.

Solution

  • You could try to create a template method applyMethod. Here is a complete example. I used an Executor class containing only one static method because I could not find a better way to process methods taking any types of parameters

    #include <iostream>
    #include <string>
    
    enum ENUM {SINGLE, PAIR};
    // General data type
    template<ENUM T, class U>class Data {
    };
    // Partially specialized for single objects
    template<class U>
    class UData : public Data<ENUM::SINGLE, U>, public U {
      // Forward Constructors, ...
    public:
            UData(const U& u): U(u) {};
    };
    // Partially specialized for pairs of objects
    template<class U>
    class PData : public Data<ENUM::PAIR, U>, public std::pair<U,U> {
      // Forward Constructors, ...
    public:
            PData(const U& u1, const U& u2): std::pair<U, U>(u1, u2) {};
    };
    
    template <class U, typename... P>
    class Executor {
            Executor() = delete;
    public:
            template<void (U::*M)(P... params)>
            static void applyMethod(Data<ENUM::SINGLE, U> &data, P ...params) {
                    UData<U>& ud= reinterpret_cast<UData<U>& >(data);
                    U& u = static_cast<U&>(ud);
                    (u.*M)(params...);
            }
            template<void (U::*M)(P... params)>
            static void applyMethod(Data<ENUM::PAIR, U> &data, P ...params) {
                    PData<U>& pd = reinterpret_cast<PData<U>& >(data);
                    (pd.first.*M)(params...);
                    (pd.second.*M)(params...);
            }
    };
    
    class X {
            std::string name;
    public:
            X(const std::string& name): name(name) { };
    
            void doStuff(void) {
                    std::cout << "DoStuff : " << name << std::endl;
            }
            void doStuff(int i) {
                    std::cout << "DoStuff : " << name << " - " << i << std::endl;
            }
    };
    
    int main() {
            X x1("x1");
            X x2("x2");
            X x3("x3");
    
            UData<X> data1(x1);
            PData<X> data2(x2, x3);
    
            Executor<X>::applyMethod<&X::doStuff>(data1);
            Executor<X, int>::applyMethod<&X::doStuff>(data2, 12);
    
            return 0;
    }