Search code examples
c++inheritanceoperator-overloadingvirtualfriend-function

why is cout calling parent operator<< instead of child operator<<


I have 3 classes, and I'd like each one to print out differently to the terminal, I have a node class that represents a vertex in a BDD graph, right now I'm trying to write code to do logical operations on the nodes.

The Node class is setup as such:

class Node {

    char name;

    public:
        Node() { name = '0'; }
        Node(char c) { name = c; }

        Node(const Node& n) { name = n.name; }

        friend ostream& operator<<(ostream& stream, const Node& n);
};

ostream& operator<<(ostream& stream, const Node& n) {
    return stream << "{ Node " << n.name << " }";
}

The operator classes are setup as such:

class Operation {
    public:

    Node result;

    friend std::ostream& operator<<(std::ostream& stream, const Operation& op);

    Operation() {}

    Operation(const Operation& op) { cout << "Copying " << *this << endl; }

    virtual ~Operation() { cout << "Destroying " << *this << endl; }

    virtual Node compute() { 
        cout << "Computing " << *this << endl; 
        result = Node('1');
        return result; 
    }
};

std::ostream& operator<<(std::ostream& stream, const Operation& op) { 
    return stream << "Operation { Unspecified }"; 
}


class UnaryOperation : public Operation {
    public:

    Node arg1;

    UnaryOperation(const Node& arg1) { this->arg1 = arg1; }

    UnaryOperation(const UnaryOperation& op) : Operation::Operation(op) {
        arg1 = op.arg1;
    }

    virtual ~UnaryOperation() {}

    friend ostream& operator<<(ostream& stream, const UnaryOperation& op);
};

ostream& operator<<(ostream& stream, const UnaryOperation& op) {
    return stream << "Operation { arg1: " << op.arg1 << " }";
}


class BinaryOperation : public UnaryOperation {
    public:

    Node arg2;

    BinaryOperation(const Node& arg1, const Node& arg2) : UnaryOperation(arg1) { 
        this->arg2 = arg2; 
    }

    BinaryOperation(const BinaryOperation& op) : UnaryOperation::UnaryOperation(op) {
        arg2 = op.arg2;
    }

    virtual ~BinaryOperation() {}

    friend ostream& operator<<(ostream& stream, const BinaryOperation& op);
};

ostream& operator<<(ostream& stream, const BinaryOperation& op) {
    return stream << "Operation { arg1: " << op.arg1 << ", arg2: " << op.arg2;
}

For debugging reasons, these messages need to print out as such, but when I run this

Node apply(Operation& op) {
    cout << "Performing apply operation on " << op << endl;
    op.compute();
    return op.result;
}


int main() {
    Node a('a'), b('b');
    UnaryOperation uop(a);
    BinaryOperation bop(a, b);
    cout << uop << endl;
    cout << bop << endl;
    apply(uop);
    apply(bop);
}

I get

Operation { arg1: { Node a } }
Operation { arg1: { Node a }, arg2: { Node b }
Performing apply operation on Operation { Unspecified }
Computing Operation { Unspecified }
Performing apply operation on Operation { Unspecified }
Computing Operation { Unspecified }
Destroying Operation { Unspecified }
Destroying Operation { Unspecified }

Needless to say, this is not very helpful for debugging.

Why is it doing this, and how do I fix it?


Solution

  • The friend function is not virtual. It is selected according to the type of the second argument.

    In this function

    Node apply(Operation& op) {
        cout << "Performing apply operation on " << op << endl;
        op.compute();
        return op.result;
    }
    

    the type of the argument is Operation &. So in this statement

    cout << "Performing apply operation on " << op << endl;
    

    there is called the friend function for an object of the type Operation &.

    You could make the friend function "virtual" the following way as it is shown in the demonstrative program below.

    #include <iostream>
    
    class Operation 
    {
    public: 
        virtual ~Operation() = default;
    
    private:    
        virtual std::ostream & out( std::ostream &os = std::cout ) const
        {
            return os << "This is an Operation"; 
        }
    
        friend std::ostream& operator <<( std::ostream &stream, const Operation &op ) 
        { 
            return op.out( stream ); 
        }
    };
    
    class UnaryOperation : public Operation 
    {
        std::ostream & out( std::ostream &os = std::cout ) const override
        {
            return os << "This is an Unary Operation";
        }
    
        friend std::ostream& operator <<( std::ostream &stream, const UnaryOperation &op ) 
        { 
            return op.out( stream ); 
        }
    };
    
    class BinaryOperation : public UnaryOperation 
    {
        std::ostream & out( std::ostream &os = std::cout ) const override
        {
            return os << "This is a Binary Operation";
        }
    
        friend std::ostream& operator <<( std::ostream& stream, const BinaryOperation &op ) 
        { 
            return op.out( stream ); 
        }
    };
    
    void f( const Operation &op )
    {
        std::cout << op << '\n';
    }
    
    int main() 
    {
        BinaryOperation bop;
    
        f( bop );
    
        return 0;
    }
    

    Its output is

    This is a Binary Operation