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