Как избежать приведения, когда возвращаемые типы не известны во время компиляции? - PullRequest
1 голос
/ 10 июля 2019

Предположим, у меня есть абстрактный базовый класс с именем Node.

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

    Node * leftChild, *rightChild;

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

У меня также есть несколько функций (из которых я буду включать две для простоты, но на самом деле это может быть любое число).

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

Для каждой функции есть связанный класс. Первый выглядит следующим образом.

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

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

Второй ниже.

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

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

В основном я хотел бы выполнить что-то подобное нижеприведенному как способ создания связанного списка в надежде построить абстрактное синтаксическое дерево.

BinaryFunction1 testBinaryFunction1();
BinaryFunction2 testBinaryFunction2();

testBinaryFunction1.attach(&testBinaryFunction2, &testBinaryFunction2);

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

dynamic_cast действительно ужасен, и я вижу, что он споткнул меня еще дальше по дороге. Есть ли способ избежать этого и полностью избавиться от него.

Насколько я понимаю, Node * leftChild, * rightChild действительно является проблемой, поскольку я считаю, что именно здесь происходит неявное понижение рейтинга. Я не уверен, как объявить эти указатели, если я не знаю, какими будут их типы во время компиляции.

1 Ответ

2 голосов
/ 10 июля 2019

Мой подход будет выглядеть примерно так:

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;
    }
};

Я не знаю, что именно вы хотите делать с указателями функций, которые вы в настоящее время пытаетесь вернуть, но это, вероятно, связано с оценкойвыражение.Эта концепция может входить в интерфейс Node и может быть реализована каждым конкретным типом узла.Для этого требуется указать тип возвращаемого значения, а это не известно на уровне Node.На самом деле, он предположительно неизвестен во время компиляции в целом - неправильный ввод пользователя, очевидно, не может привести к ошибкам во время компиляции, это должно привести к ошибкам во время выполнения.std::variant здесь хорошо подходит (но ограничивает вас набором типов во время компиляции, что, вероятно, достаточно).

Затем мы можем определить, например,

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

и использовать всевместе вот так:

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());
}

(Обратите внимание, что для полиморфизма нужны указатели или ссылки!)

Поиграйте с этим здесь: https://godbolt.org/z/GNHKCy

...