jeudi 29 avril 2021

extending class and its data member

I have a class A that has a data member data of type base_data. I would like to extend A to B by inheritance, but at the same time extend A::data to a derived type derived_data.

One possible design for that is to declare A as a template class, accepting as parameter the type of A::data.

struct base_data {
};

struct derived_data : public base_data {
};

template <class DataType = base_data>
struct A {
    DataType data;
};

struct B : public A<derived_data> {
};

The problem with this design is that, strictly speaking, B is not derived from A<>, hence functions that involve an A<> object would not compile with a B type. Example:

void f(A<>&&) { }
void f(const A<>&) { }

A x, y;
f(x);
f(std::move(y));
B xB, yB;
f(xB); // Error
f(std::move(yB)); // Error

One workaround is to declare f as template function

template <class T>
void f(A<T>&&) { }
template <class T>
void f(const A<T>&) { }

This however has the drawback that f can be called with any A<T>, which may not be desirable.

Another approach would be to constraint the argument of f to be either A<> or B

template <typename T, class = std::enable_if_t<std::is_same_v<T, A<>> || std::is_same_v<T, B>>>
void f(const T& a) { }

template <typename T, class = std::enable_if_t<std::is_same_v<T, A<>> || std::is_same_v<T, B>>>
void f(A<>&& a) { }

This is also not very elegant. Essentially, the part of a code that contains functions using A now must know that it could also use a sort-of-derived class B, perhaps defined elsewhere. Also, if there are many of such functions, one has to modify all of them to template functions as above. And if another sort-of-derived class C is added, one has to update all declarations of f and similar.

A third option would be to provide explicit conversions from B to A<>

template <class DataType = base_data>
struct A {
    DataType data;
    A() { }
    A(B&&) { }
    A(const B&) { }
};

But again, this implies that the code of A must know its sort-of-descendant B. Even worse, the copy conversion A(const B&) may be expensive and defies the simple idea that a const reference can bind to a derived class, without additional overhead.

Is there a good/better design pattern for this problem?

Aucun commentaire:

Enregistrer un commentaire