1

I have a program in which I would like to choose a set of types (from predefined lists) at run time, not compile time.

Below is an example of the kind of code I'd like to run; Even and Log are types to define a numerical grid, and deriv_Ox is a differentiation scheme of order x:

struct Even {
  double a, b;
};
struct Log {
  double a, x0, b;
};
// ...

struct deriv_O2 {
  vec_type operator() (const vec_type & f_points) const;
};
struct deriv_O4 {
  vec_type operator() (const vec_type & f_points) const;
};
// ...

template <class grid_type, class deriv_type>
void run_calculation (const std::string param_file) {
  auto grid  =  grid_from_file<grid_type>(param_file);
  auto deriv = deriv_from_file<deriv_type>(param_file);
  // ...
}

I would like to decide which of these types to use at run time, by reading a parameter file. My solution was to use tags and case statements to decide which type to use from a single list, then nest each case statement in a function deciding each type in the set as follows:

enum struct grid_tag { Even, Log };
enum struct deriv_tag { O4, O2 };

grid_tag grid_tag_from_file (const char file_name[]);
deriv_tag deriv_tag_from_file (const char file_name[]);

template <class deriv_type>
void run_calculation (const grid_tag g,
                      const std::string param_file) {
  switch(g) {
  case grid_tag::Even:
    run_calculation<Even, deriv_type>(param_file);
    break;
  case grid_tag::Log:
    run_calculation<Log, deriv_type>(param_file);
    break;
  }
}

void run_calculation (const grid_tag g, const deriv_tag d,
                      const std::string param_file) {
  switch(d) {
  case deriv_tag::O4:
    run_calculation<deriv_O4>(g, param_file);
    break;
  case deriv_tag::O2:
    run_calculation<deriv_O2>(g, param_file);
    break;
  }
}

int main (int argc, char * argv[]) {
  grid_tag g = grid_tag_from_file(argv[1]);
  deriv_tag d = deriv_tag_from_file(argv[1]);
  run_calculation(g, d, argv[1]);
}

The problem is that I have a set of ~6 types to choose from lists of size ~10, and these will grow in the future. The solution I have at the moment makes adding new types awkward.

Is this solution the best I'm going to do? Am I being very fussy, or is there a better solution someone can suggest? I have looked at boost::variant (as recommended in similar questions) but I don't think this is really suitable for what I want to do.

F Chambers
  • 33
  • 4

3 Answers3

2

As written, this leads to "double dispatch", which is not an easy thing to solve in C++ (see e.g. here: Understanding double dispatch C++).

What might be applicable in this case, instead of having:

template <class grid_type, class deriv_type>
void run_calculation (const std::string param_file) {
  auto grid  =  grid_from_file<grid_type>(param_file);
  auto deriv = deriv_from_file<deriv_type>(param_file);
  // ...
}

to retrieve the grid/deriv from the file and result in a concrete type, to have instead

void run_calculation (const std::string param_file, grid_tag gtag, deriv_tag dtag) {
  auto /*Grid interface*/ grid  = grid_from_file(param_file, gtag);
  auto /*Deriv interface*/ deriv = deriv_from_file(param_file, dtag);
  // ...
}

and using virtual function call on the Grid/Deriv interface to do the stuff.

(if you do not want to pollute the original grid/deriv classes by the virtual methods, you could also create wrappers for them)

The advantage of this (of course if applicable to your actual situation) would be, that you would not need to solve all the combinations. Compared to the "switch" solution (works in a similar way), you do not need to remember to put switches everywhere to decide the types, you can just call the appropriate virtual functions to do the work (and if the virt. functions are pure in the interface, you cannot forget to provide them, because it will not compile otherwise).

Also, instead of grid_tag, deriv_tag, you could provide a virtual method on the interfaces to read from the file appropriately.

And I would also recommend to pass the string by const ref ("const std::string & param_file"), not by value (copies made).

EmDroid
  • 5,918
  • 18
  • 18
1

Selecting a type from a runtime value inherently have some ugliness involved, but judging from the snippet provided, a table of functions would work just fine

enum struct grid_tag { Even, Log, size };
enum struct deriv_tag { O4, O2, size };

using calculation = void (*)(std::string);

calculation table[grid_tag::size][deriv_tag::size];  // populate them

void run_calculation (const grid_tag g, const deriv_tag d, const std::string& param_file)
{
    table[g][d](param_file);
}
Passer By
  • 19,325
  • 6
  • 49
  • 96
0

You can solve this creating several interface (abstract virtual classes with no method implemented) , one for each type you want to decide at runtime.

You then can use a template method pattern to write your algorithm using the interfaces you have written.

This way, adding elements to the lists of type is just adding a new class implementing the interface.

bracco23
  • 2,181
  • 10
  • 28