Reputation: 75
I have a large and complex program that makes use of templates quite extensively. For the sake of simplicity, consider I have the following classes in my program:
BronzeFork , SilverFork, GoldFork
SimpleKnife , SerratedKnife, ButterKnife
RedSpoon , GreenSpoon, BlueSpoon
and then in main.cpp I call a function:
Eating<BronzeFork, SerratedKnife, BlueSpoon>::runEat();
My aim is to be able to be able to feed arguments at runtime to my (compiled) program in order to modify the kind of fork or spoon that I want in this run.
I could do it with a very large switch statement running every combination of forks, knives and spoons but it seems quite messy, convoluted and hard to maintain. Especially because I am continuously adding kinds of Forks, Knifes and Spoons into the program.
What would be the best way to do this in a clean, efficient way?
Upvotes: 0
Views: 459
Reputation: 119877
This is not necessarily the best way to solve your problem. This is one way to generate a lot of instantiations in a compact manner. It may or may not suit your need.
I assume each of your classes have some kind of ID for run-time selection, say a string. So BronzeFork::id
would be "BronzeFork"
. I will use abbreviated names henceforth.
#include <tuple>
#include <string>
#include <functional>
#include <map>
struct F1 { static constexpr const char* id = "F1"; };
struct F2 { static constexpr const char* id = "F2"; };
struct F3 { static constexpr const char* id = "F3"; };
struct K1 { static constexpr const char* id = "K1"; };
struct K2 { static constexpr const char* id = "K2"; };
struct K3 { static constexpr const char* id = "K3"; };
struct S1 { static constexpr const char* id = "K1"; };
struct S2 { static constexpr const char* id = "K2"; };
struct S3 { static constexpr const char* id = "K3"; };
template <typename F, typename K, typename S> struct Eater
{
static void eat();
};
using t3 = std::tuple<std::string, std::string, std::string>;
std::map<t3, std::function<void()>> funcMap;
Now we need to populate funcMap
. We need nested compile-time loops. I implement them as separate templates using fold-expressions. There might be a better way, but this one is very unsophisticated and straightforward so it's easy to understand.
template <typename F, typename K, typename S> struct populate0
{
void operator()() { funcMap.insert({{F::id, K::id, S::id}, Eater<F, K, S>::eat}); }
};
// Inner loop
template <typename Fs, typename K, typename S> struct populate1;
template <typename ... Fs, typename K, typename S>
struct populate1 <std::tuple<Fs...>, K, S> // loop over Fs
{
void operator()() { (populate0<Fs, K, S>()(), ...); }
};
// Middle loop
template <typename Fs, typename Ks, typename S> struct populate2;
template <typename Fs, typename ... Ks, typename S>
struct populate2 <Fs, std::tuple<Ks...>, S> // loop over Ks
{
void operator()() { (populate1<Fs, Ks, S>()(), ...); }
};
// Outer loop
template <typename Fs, typename Ks, typename Ss> struct populate3;
template <typename Fs, typename Ks, typename... Ss> // loop over Ss
struct populate3 <Fs, Ks, std::tuple<Ss...>>
{
void operator()() { (populate2<Fs, Ks, Ss>()(), ...); }
};
Now we need to just run the loop.
populate3<std::tuple<F1, F2, F3>,
std::tuple<K1, K2, K3>,
std::tuple<S1, S2, S3>>()();
Whenever you add another class, add it to this call.
Upvotes: 1
Reputation:
C++ is a compilable language, meaning that the types must be known at code-generation-time. Virtual classes could be a solution to your case.
This is not incompatible with the use of templates, which can keep the source code compact. But all specializations with the types you can meet at run-time must be instantiated.
Upvotes: 1