Tengo una secuencia de objetos de tarea que me gustaría encadenar de manera que la salida de una tarea sea la entrada a la siguiente.

template <typename Input, typename Output> class Task {
public:
  std::function<Output(Input)> func;

  Task(std::function<Output(Input)> func) : func(func){};
  Output run(const Input& input) { return func(input); }
};

Genial, ahora puedo encadenar algunas tareas.

#include <iostream>
#include "Task.h"

int main() {
  auto func1 = [](int i) {
    return i + 1;
  };
  auto func2 = [](std::string i) {
    return stoi(i);
  };

  Task<int, int> task1(func1);
  Task<std::string, int> task2(func2);

  std::cout << task1.run(task2.run("1"));
  return 0;
}

Pero ahora, me gustaría poner las tareas en un contenedor TaskList para no tener que codificar estas cadenas de tareas. La API podría tener el siguiente aspecto:

class TaskList {
public:
  std::vector<ITask> tasks;

  void append(ITask task) { tasks.push_back(task); }
  void run() { // Run all tasks }
};

Donde ITask es una clase base para los distintos tipos de Task<Input, Output>. ¿Cómo podría escribir esas clases ITask y TaskList? Hay muchas formas de mezclar dónde se introducen los tipos (por ejemplo, constructor Task, método run), pero no he encontrado una manera de generalizar a TaskList.

¿Es este enfoque totalmente incorrecto? ¿O lo que estoy intentando hacer es un mal diseño?

0
danielsuo 6 dic. 2016 a las 00:50
3
Tienes un problema. Si usa un contenedor general, los tipos de entrada / salida en cada etapa serán desconocidos en el momento de la compilación. Esto significa que deberá almacenar el resultado de cada etapa en un variant o any y luego la siguiente etapa debe convertir la variante en el tipo correcto (o convertirla o lanzarla)
 – 
Richard Hodges
6 dic. 2016 a las 00:54
O si los tipos de E / S de la tarea se conocen en el momento de la compilación, un tuple
 – 
AndyG
6 dic. 2016 a las 00:55
Ah, esperaba evitar seguir este camino, pero sí, los tipos de entrada / salida son desconocidos en el momento de la compilación. También podría tener una clase base para todos mis tipos de entrada / salida, pero eso es bastante malo ya que es posible que no tenga control sobre todos los tipos.
 – 
danielsuo
6 dic. 2016 a las 01:15

1 respuesta

La mejor respuesta

¿Qué pasa con el uso de la recursividad y la especialización de plantillas, en lugar de un std::vector?

El siguiente debería ser un ejemplo de trabajo

#include <functional>
#include <iostream>

template <typename, typename ...>
class TaskList;

template <typename First>
class TaskList<First>
 {
   public:
      TaskList () 
       { }

      First run (First const & firstVal) const
       { return firstVal; }
 };

template <typename Output, typename Input, typename ... Prevs>
class TaskList<Output, Input, Prevs...>
 {
   private:
      std::function<Output(Input)> const  head;
      TaskList<Input, Prevs...>    const  tail;

   public:
      template <typename ... Funcs>
      TaskList (std::function<Output(Input)> const & f0,
                Funcs const & ... funcsPre)
         : head {f0}, tail {funcsPre...}
       { }

      template <typename T>
      Output run (T const & firstArg) const
       { return head(tail.run(firstArg)); }
 };

int main ()
 {
   auto func0 = [](int i) { return long(i << 1); };
   auto func1 = [](int i) { return i + 1; };
   auto func2 = [](std::string i) { return stoi(i); };

   TaskList<long, int, int, std::string> taskL012(func0, func1, func2);

   std::cout << taskL012.run("1") << std::endl; // print 4
 }
0
max66 6 dic. 2016 a las 04:45
Creo que funcionará. Pero a diferencia del uso habitual de la recursividad y la especialización de plantillas, que normalmente no agota los límites de implementación, creo que el OP podría estar buscando algo con menos restricción de cuánto tiempo podría correr la cadena.
 – 
Yan Zhou
6 dic. 2016 a las 03:45
¡Gracias por esta interesante solución! Sin embargo, además del comentario de @ YanZhou, también parece que debería especificar la cadena de tareas en el momento de la compilación. Mi aplicación podría cambiar esto, pero tendré que pensar más. Con respecto a std::vector, mi culpa por no mencionar en la publicación original; Es posible que desee acceso aleatorio a tareas individuales para modificarlas o intercambiarlas.
 – 
danielsuo
6 dic. 2016 a las 18:29
- ¿Necesita especificar la cadena de tareas en el momento de ejecución ? ¡Guau! Si es así, es un escenario completamente diferente. Debe especificar esto en su pregunta y mostrar un ejemplo del código que desea escribir. ¿Tiene privilegios de edición para sus preguntas? De lo contrario, debe abrir otra pregunta o, si puede explicarlo mejor, puedo editar la respuesta actual por usted.
 – 
max66
6 dic. 2016 a las 20:16
@ max66: redefiní las API que usará mi aplicación, así que ahora necesito que se especifique el tiempo de compilación de la cadena de tareas, así que esto funciona bien, ¡gracias! No pensé en usar plantillas recursivas.
 – 
danielsuo
7 dic. 2016 a las 00:30