Johannes
Johannes

Reputation: 3099

Forward declare other nested struct in C++

I'm trying to implement the visitor pattern inside of another class. MWE:

struct super
{
    struct base
    {
        virtual void accept(struct visitor& v);
        virtual ~base() {}
    };

    struct visitor
    {
        virtual void visit(base& b);
        virtual ~visitor() {}
    };

    struct special : public base
    {
        void accept(visitor& v) override { v.visit(*this); }
    };
};

int main() {}

This complains that special::accept is actually not overriding anything. I guess this is because of struct visitor being different to visitor.

Swapping the position of base and visitor (and moving the forward declaration to visitor::visit) vanishes this error (but then says that the argument in v.visit(*this) would not match).

Is it possible to implement the visitor pattern inside another class? Why does my forward declaration not work?

Upvotes: 5

Views: 354

Answers (2)

Declaring

struct super
{
    struct base
    {
        virtual void accept(struct visitor& v);
        virtual ~base() {}
    };
};

Does not make visitor a member of base nor a member of super. It actually forward declares global ::visitor. This is why in C++, it is considered very bad style to forward declare a type inside some other declaration. Base class function accept has the signature void accept(::visitor&) but derived class has signature void accept(super::visitor&). The MWE you wrote would be equivalent to the following code:

struct super
{
    struct base
    {
        // Declares ::visitor
        // Same signature as: virtual void accept(::visitor&);
        virtual void accept(struct visitor& v);
        virtual ~base() {}
    };

    // Declares super::base::visitor
    struct visitor
    {
        virtual void visit(base& b);
        virtual ~visitor() {}
    };

    struct special : public base
    {
        // Must have signature void accept(::visitor&) to override
        void accept(::visitor& v) override;
    };
};

struct visitor
{
    virtual void visit(super::base& b);
    virtual ~visitor() {}
};

inline void super::special::accept(::visitor& v) { v.visit(*this); }

int main() {}

Upvotes: 1

NathanOliver
NathanOliver

Reputation: 180945

When you do

virtual void accept(struct visitor& v);

you forward declare visitor in the smallest namespace or block scope that contains the declaration. That means visitor is scoped to the global namespace in this case.specials`'s

void accept(visitor& v)

on the other hand is grabbing super::visitor. Since those are different types the compiler is correct.

What you need to do is move the forward declaration of visitor into super's scope like

struct super
{
    struct visitor;
    struct base
    {
        virtual void accept(visitor& v);
        virtual ~base() {}
    };

    struct visitor
    {
        virtual void visit(base& b);
        virtual ~visitor() {}
    };

    struct special : public base
    {
        void accept(visitor& v) override { v.visit(*this); }
    };
};

int main() {}

Upvotes: 5

Related Questions