einpoklum
einpoklum

Reputation: 132128

What should I break: Substitution capability or the Liskov Substitution Principle?

Suppose I have the following class:

template<typename T>
struct A { T* ptr; };

and a bunch of functions which take this class as a parameter, e.g.:

template <typename T>
int one_two_three(A<T> a) { return 123; }

Now, I want to define a class which is:

Now comes my dilemma. Here are two such A-like classes, named B and C:

template<typename T>
class B {
public:
    using a_type = A<T>;

    B(T* ptr) : a_{ptr} { }
    a_type get() const noexcept { return a_; }
    operator a_type() const noexcept { return get(); }

private:
    a_type a_;
}; 

template<typename T>
class C : public A<T> {
public:
    using a_type = A<T>;

    C(T* ptr) : a_type{ptr} { }
    a_type get() const noexcept { return *this; }
    operator a_type() const noexcept { return get(); }
};

If I use class B, I am unable to substitute A's in calls to one_two_three().

On the other hand, if I use class C - I am able to substitute A's in calls to one_two_three(); but then I am breaking the Liskov substitution principle: C is a subtype of A, but one cannot replace an instance of C wherever an instance of A is used, since C is more restricted.

Can I somehow achieve both the benefits of B and of C in the same class? Or must I choose between these two approaches?

Note:


Motivation: My A is actually a span class, and B or C adds ownership semantics, like a unique_ptr but with a size, and should be usable where I would use a span.

Upvotes: 1

Views: 94

Answers (0)

Related Questions