Я пытаюсь построить график выполнения, в котором каждый узел может создавать несколько выходных данных различных типов и использовать несколько входных данных различных типов.Данные передаются между узлами через очереди.
Чтобы соединить 2 узла, тип входа одного узла должен быть подключен к тому же типу вывода другого узла.Например, попытка подключить выход int
одного узла к входу double
другого узла приведет к ошибке компиляции.
Все узлы будут производными от базового класса, который имеет execute()
метод, который читает из различных типов ввода и записывает в различные типы вывода.В настоящее время у меня есть что-то вроде этого -
struct Node {
virtual void execute() = 0;
};
struct IntegerGeneratorNode: public Node {
void execute() {
while(some_condition_is_not_met) {
// write() will do std::queue<int>::push();
int_out.write(some_rand_integer);
}
}
Output<int> int_out;
};
struct FloatGeneratorNode: public Node {
void execute() {
while(some_condition_is_not_met) {
// write() will do std::queue<float>::push();
float_out.write(some_rand_float);
}
}
Output<float> float_out;
};
struct SinkNode: public Node {
void execute() {
while(some_condition) {
int val = int_inp.read(); // invokes queue<int>::front()+pop()
float f_val = float_inp.read();
// Do something with val and f_val.
}
}
Input<int> int_inp;
Input<float> float_inp;
};
Input<T>
- шаблонный класс, который имеет queue
типа T
.Output<T>
является шаблонным классом, который имеет указатель на queue
типа T
.Чтобы соединить 2 узла, я делаю что-то вроде -
Node *int_node = IntegerGeneratorNode();
Node *float_node = FloatGeneratorNode();
Node *sink_node = SinkNode();
int_node.int_out.connect(sink_node.int_inp);
float_node.float_out.connect(sink_node.float_inpt);
std::thread int_thread([](Node *node){ node->execute(); }, int_node);
std::thread float_thread([](Node *node){ node->execute(); }, float_node);
std::thread sink_thread([](Node *node){ node->execute(); }, sink_node);
Это работает нормально, но у меня есть другое требование - выполнить некоторую операцию на ВСЕХ Input<T>
с Node
до того, как метод execute
называется - Input<T>::doSomePreProcessing()
.Как видите, именованные переменные не масштабируются.Вы должны добавить .doSomething
на ВСЕ переменные, которые у вас есть.Мне нужен какой-то цикл.
Одна идея состоит в том, чтобы иметь tuple
из Input
типов и повторять кортеж, используя C ++ 17 std::apply
.Но у меня есть еще одно важное требование: производные классы являются клиентским кодом, а базовый класс - структурным кодом.Вся предварительная обработка должна выполняться из базового класса, чтобы уменьшить нагрузку на производные классы.Для этого мне нужно переместить кортеж в базовый класс следующим образом:
template<typename T>
struct Node {
virtual void execute() = 0;
void doExecute {
preprocess(some_tuple);
execute();
}
T inputs() { return input_tuple; }
T input_tuple;
};
template<typename T>
struct SinkNode: public Node<T>{
...
};
// Call site.
SinkNode<std::tuple<int,float>> sink_node;
2 побочных эффекта произошло из-за этого изменения
Базовый класстеперь я не могу иметь общий указатель базового класса, который требуется для вызова метода doExecute
в узлах графа.
connect
трудно реализовать.В идеале я ищу
template<typename T>
void connect(Node* src, Node* dest) {
std::get<T>(src.outputs()).connect(std::get<T>(dest.inputs()));
}
Но поскольку Node*
больше не доступен, я не могу этого сделать.
Как мне все сделатьОбработка на входах в базовом классе еще имеет общий указатель базового класса?Другими словами, это не шаблонно?