Peter
Peter

Reputation: 45

Define member functions once for two similar classes?

Is it possible to avoid defining the same functions for structs A, B below twice? Their members are named exactly the same, but the v0, v1 ... vN members are of different type between the two structs A and B. If it helps the members v* are all derived from the same struct V.

Non-trivial functions (i.e. assignment =) can reuse a templated copy function outside the structs, like below, but it'd be preferred/cleaner if this was defined once and within the struct.

Is there a clean way to templatize A and B definition into a single one?

template <class T1, class T2>
void copy(T1& to, T2& from)
{
    to.v0 = from.v0;
    to.v1 = from.v1;
    to.type = from.type;
}

enum class E { TYPE_0, TYPE_1 };

struct B;

struct A 
{
    C0<float> v0;
    C1<int> v1;
    E type;

    A& operator = (const B& t)
    {
        copy(*this, t);
        return *this;
    } 
    
    string strType() { return string(type); }    
};

struct B
{
    D0<float> v0;
    D1<int> v1;
    E type;

    B& operator = (const A& t)
    {
        copy(*this, t);
        return *this;
    } 

    string strType() { return string(type); }
}

Upvotes: 3

Views: 85

Answers (2)

JeJo
JeJo

Reputation: 32722

You can keep a common base template class for the common functionalities and inherit from it for class A and B.

However, the copy assignment operator is a special member function, which I do not think one could template for different types return in the base class. Therefore you need to provide for each class.

Meaning, you can do

enum class E { TYPE_0, TYPE_1 };
struct B;

template<typename ClassA, typename ClassB> struct Common
{
   template<E type> 
   std::string strType() { return std::to_string(static_cast<int>(type)); }
   // need to cast the type to int before you convert std::to_string

   // other common-member-functions
};

struct A : public Common<A, B>
{
   C0 v0;
   C1 v1;
   E type;
   // ... A& operator = (const B& t)
   // bring the common functionalities to A
   using Common<A, B>::strType;
   // ... other member-functions
};

struct B : public Common<B, A>
{
   D0 v0;
   D1 v1;
   E type;
   // ... B& operator = (const A& t)
   // bring the common functionalities to A
   using Common<B, A>::strType;
   // ... other member-functions
};

However, both structs A, B, seems to only differ from the two members (i.e. C0, C1 and D0, D1 respectively), combining both classes to one by making a class-template, also an alternative:

Following is an example code:

#include <iostream>
#include <vector>
#include <string>

enum class E { TYPE_0, TYPE_1 };

template<typename T1,  typename T2>
struct AandB
{
   T1 v0;
   T2 v1;
   E type;
   AandB() : type{ E::TYPE_0 } {}
   AandB& operator= (const AandB& rhs) // one operator =
   {
      v0 = rhs.v0;
      v1 = rhs.v1;
      type = rhs.type;
      return *this;
   }

   std::string strType() const { return std::to_string(static_cast<int>(type)); }
};

int main()
{
   using C0 = std::vector<float>;
   using C1 = std::vector<int>;
   AandB<C0, C1> obj;
   std::cout << obj.strType() ; // Prints: 0
}

Upvotes: 4

florestan
florestan

Reputation: 4655

This is exactly the use case for a pattern called CRTP (Curiously Recurring Template Pattern).

The idea is to define a base class template that takes the derived class as template parameter. Then you can safely cast the this in the base class to the derived class and access the derived class members in the base class.

This will also work for the copy assignment operator.

#include <string>
#include <iostream>

template<typename Derived>
struct CRTPBase {
    
    Derived& self() { return static_cast<Derived&>(*this); }
    const Derived& self() const { return static_cast<const Derived&>(*this); }

    std::string strType() { return self().type;}

    template<class OtherDerived>
    Derived& operator=(const CRTPBase<OtherDerived>& other) {
        self().type = other.self().type;
        return self();
    }
};

struct A : public CRTPBase<A>
{
   using Base = CRTPBase<A>;
   std::string type = "A";

   using Base::operator=;
};

struct B :  public CRTPBase<B>
{
      using Base = CRTPBase<B>;
   std::string type = "B";

   using Base::operator=;
};

int main() {

    A a;
    B b;
    std::cout << a.strType() << std::endl; // Prints: A
    std::cout << b.strType() << std::endl; // Prints: B
    
    a = b;
    
    std::cout << a.strType() << std::endl; // Now prints: B
}

Live example here.

Upvotes: 2

Related Questions