Abstraction over one C ++ object and std :: pair of objects using templates

Suppose the following template construct:

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 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 a 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();
}

      

those. I want to be able to use a couple of objects (of the same type) as if it were the only object. Of course, I could override type methods T

for Data<ENUM::PAIR, T>

(see dau_sama's answer), which for this example would look like this:

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

      

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

The solution syntax may be very different from what I wrote above, this is just to demonstrate what I want to achieve. I would have preferred a solution without macros, but could live with that as well.

Is it possible to implement such an abstraction in C ++ 11?

The reasons why I want to do this are

  • I don't need to specialize templated methods that will work for ENUM::Single

    and ENUM::PAIR

    when all the differences between specializations match the above template (avoid a lot of code duplication).
  • My code repeats the same pattern very often, and I could avoid implementing workarounds in many places, which would be pretty much the same in every case.
+3


source to share


3 answers


You can try to create a template method applyMethod

. Here's a complete example. I used a class Executor

containing only one static method because I couldn't find a better way to handle methods that take any parameter types



#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;
}

      

+2


source


You can add a generic method for your classes

template<class U>
Data<ENUM::SINGLE, U> : public U {
  // Forward Constructors, ...
  void handle() {
    //do some specific handling for this type 
    return;
  }
};

      

Now someMethod will just call the right "handle" and it will automatically switch between the two



template<typename T>
someMethod(T& data) {
  data.handle();
}

//If you want to bind your function to some other name, you could
//create a functor that calls someMethod with the arguments passed in _1
//I haven't tested it, there might be some syntax problems with the way you pass in the function name
auto someOtherMethod = std::bind (someMethod, _1);

      

If your type does not implement a descriptor method, you will have a nasty compilation error. If you want to provide a default implementation and avoid a compile-time error, there is a generic pattern called SFINAE (Replacement Failure Is Not a Bug) that does just that.

0


source


Here's an alternative to the solution from Serge Balesta using lambda.

#include <functional>

template<ENUM T, class U>void for_single_or_pair(
    Data<T, U>& data,
    std::function<void(U&)> function);

template<class U>void for_single_or_pair(
    Data<ENUM::SINGLE, U>& data,
    std::function<void(U&)> function) {
  function(data);
}

template<class U>void for_single_or_pair(
    Data<ENUM::PAIR, U>& data,
    std::function<void(U&)> function) {
  function(data.first);
  function(data.second);
}

      

Using:

template<ENUM T>someMethod(Data<T, SomeClass> data) {
  for_single_or_pair(data,[](SomeClass& someObject) {
    // Play around with someObject in any way
  });
}

      

Thus, to use the methods of the members of SomeClass, the data can be used in any other way.

I would be happy to comment on this solution (and if it could be generalized to use more than one Data inside the for_single_or_pair method).

0


source







All Articles