How to avoid down-casting when return types are no

2019-07-26 23:59发布

Suppose I have a abstract base class called Node.

class Node
{
public:
    Node() {
        leftChild = NULL;
        rightChild = NULL;
    };

    Node * leftChild, *rightChild;

    void attach(Node * LC, Node * RC) {
        leftChild = LC;
        rightChild = RC;
    };
};

I also have multiple functions (of which I'll include two for simplicity but in reality this could be any number).

float add(float a, float b){return a+b;}
bool gt(float a, float b){return a>b;}

For each function there is an associated class. The first is as follows.

class BinaryFunction1 : public Node
{
public:
    BinaryFunction1() {
    };

    float(*)(float, float) addition(){
        return add
    };
}

The second is below.

class BinaryFunction2 : public Node
{
public:
    BinaryFunction2() {
    };

    bool(*)(float, float) greaterthan(){
        return gt
    };
}

In main I would like to execute something like the below as a way of creating a linked list in the hopes of building an abstract syntax tree.

BinaryFunction1 testBinaryFunction1();
BinaryFunction2 testBinaryFunction2();

testBinaryFunction1.attach(&testBinaryFunction2, &testBinaryFunction2);

dynamic_cast<BinaryFunction2 *>(testBinaryFunction1.leftChild)->greaterthan()(2.0, 4.0)

The dynamic_cast is really ugly and I see it tripping me up further down the road. Is there a way to avoid this and get rid of it entirely.

As far as I can see Node * leftChild, * rightChild is really the problem as I believe this is where the implicit down-casting happens. I am unsure how to declare these pointers if I don't know what their types will be at compile time.

1条回答
迷人小祖宗
2楼-- · 2019-07-27 00:38

My approach would look something like this:

using TypedValue = std::variant<int, float, bool>;

using BinaryFunc = std::function<TypedValue(TypedValue, TypedValue)>;

struct Node
{
public:
    Node() {
        leftChild = nullptr;
        rightChild = nullptr;
    };

    virtual ~Node() = default;

    Node * leftChild, *rightChild;

    void attach(Node * LC, Node * RC) {
        leftChild = LC;
        rightChild = RC;
    };

    virtual TypedValue evaluate() = 0;
};


struct BinaryFuncNode : public Node
{
    BinaryFuncNode(BinaryFunc func) : Node(), binaryFunc(func) {}

    BinaryFunc binaryFunc;

    TypedValue evaluate() override
    {
        return binaryFunc(leftChild->evaluate(), rightChild->evaluate());
    }
};

struct ConstantNode : public Node
{
    ConstantNode(TypedValue val) : Node(), value(val) {}

    TypedValue value;

    TypedValue evaluate() override
    {
        return value;
    }
};

I don't know what exactly you want to do with the function pointers you are currently trying to return, but it's probably got to do with evaluating an expression. That concept can go into the Node interface and can be implemented by each concrete type of node. That requires specifying a return type though, and that's not something known at the Node level. In fact, it's presumably unknown at compile-time in general - invalid user input can obviously not lead to compile-time errors, it will have to lead to run-time errors. std::variant is a good match here (but limits you to a compile-time set of types, which is probably sufficient).

We can then define e.g.

// Function that can only add integers (throws otherwise)
BinaryFunc addI = [](TypedValue lhs, TypedValue rhs)
{
    return std::get<int>(lhs) + std::get<int>(rhs);
};

and use everything together like this:

int main()
{
    auto cnode = std::make_unique<ConstantNode>(10);
    auto bfnode = std::make_unique<BinaryFuncNode>(addI);
    bfnode->attach(cnode.get(), cnode.get());
    return std::get<int>(bfnode->evaluate());
}

(Note that polymorphy requires pointers or references!)

Play around with it here: https://godbolt.org/z/GNHKCy

查看更多
登录 后发表回答