Search code examples
c++templatestemplate-specializationderived-class

C++: Use Derived-class-only variables from Base class


Imagine that I have two similar structures A and B. Both A and B have a pointer to A, but A has an additional pointer to either A or B.

I thought of something like this with a Base and a Derived class

template <bool T> struct Derived;
struct Base { Derived<0> *p1; };
template <> struct Derived<0> : public Base { Base *p2; };
template <> struct Derived<1> : public Base {};

where A is Derived<0> and B is Derived <1>.

The problem here is that when accessing a class through p2, the compiler does not know which of the Derived classes it is, and something like this gives an error.

Derived<0> x, y, z;
x.p2 = &y;
y.p2 = &z;
x.p2->p2; // Error

Do any of you know any magical workaround, preferably using only compile-time features?

I also need to know which type of Derived am I using, so that I know if I can use p2 or not.

If it helps, you can visualise things as a double-linked list, where Derived<0> is a normal node, Derived<1> is the end node, p1 is the prev pointer and p2 is the next pointer.

Edit: It does not need to use a Base-and-Derived-class-type structure, it can be anything.


Solution

  • A possible solution is based on double dispatching, the same idea that is behind the most known visitor pattern.

    It follows a minimal, working example:

    #include<iostream>
    
    template<int>
    struct Derived;
    
    struct Visitor {
        template<int N>
        void visit(Derived<N> &);
    };
    
    struct Base {
        Derived<0> *p1;
        virtual void accept(Visitor &) = 0;
    };
    
    template<>
    struct Derived<0>: public Base {
        void accept(Visitor &) override;
        Base *p2;
    };
    
    template<>
    struct Derived<1>: public Base {
        void accept(Visitor &) override;
    };
    
    template<>
    void Visitor::visit(Derived<0> &d) {
        std::cout << "Derived<0>" << std::endl;
        d.p2->accept(*this);
    }
    
    template<>
    void Visitor::visit(Derived<1> &) {
        std::cout << "Derived<1>" << std::endl;
    }
    
    void Derived<0>::accept(Visitor &v) {
        v.visit(*this);
    }
    
    void Derived<1>::accept(Visitor &v) {
        v.visit(*this);
    }
    
    int main() {
        Visitor v;
        Derived<0> x, y;
        Derived<1> z;
        x.p2 = &y;
        y.p2 = &z;
        x.p2->accept(v);
    }
    

    See it up and running on wandbox.

    If you can use C++17 and thus std::variant, things are far more simple:

    #include<iostream>
    #include<variant>
    
    template<int>
    struct Derived;
    
    struct Base {
        Derived<0> *p1;
    };
    
    template<>
    struct Derived<0>: public Base {
        std::variant<Derived<0> *, Derived<1> *> p2;
    };
    
    template<>
    struct Derived<1>: public Base {};
    
    struct Visitor {
        void operator()(Derived<0> *d) {
            std::cout << "Derived<0>" <<std::endl;
            std::visit(*this, d->p2);
        }
    
        void operator()(Derived<1> *) {
            std::cout << "Derived<1>" <<std::endl;
        }
    };
    
    int main() {
        Visitor v;
        Derived<0> x, y;
        Derived<1> z;
        x.p2 = &y;
        y.p2 = &z;
        std::visit(v, x.p2);
    }
    

    See it up and running on wandbox.