Playing with templates

Hello,

Some weeks ago, I was trying to simulate a little country : economisc, health, etc. To have a realistic simulation, there is a huge amount of parameters to consider, each of them influencing others in various ways, and each of them is unique. A good playground for some metaprogramming.

A value is updated on Update() call. It undergoes a lot of influences and maybe have to satisfy some conditions and constraints. Let's use variadic templates, and try to obtain a good final result, to have a sexy and beautiful syntax, easily extandable, almost natural english. Something like that:

// let's declare some values
Value<Lodgement>;
Value<Budget>;
Value<Population,
        Do<Population, Plus, Number<50>>,
        MustBe<LessEqThan, Logement>>;

// A main() example
int main(void)
{
    Lodgement::Set(1000);
    Population::Set(200);
    Budget::Set(5000);
    while (true)
    {
        Population::Update();
        std::cout << Parameter<Population>()
            << " people for a capacity of "
            << Parameter<Logement>() << ". Budget : "
            << Parameter<Budget>() << std::endl;
        std::cout << "Buy some lodgements ?" << std::endl;
        int rep;
        std::cin >> rep;
        if (rep)
        {
            BuyBuilding::Do();
        }
    }
    std::cout << Parameter<Logement>() << std::endl;
}

The result will be close than Value<NameOfValue, influences and constraints...>

Start with class Value

template <class... Actions>
class Value;

A simple declaration of a class with variadic templates: some policies.

Templates must be thinked in functionnal: values are not mutables, so arguments list must be used recursively. Start with the final case:

template <class T>
class Value<T>
{
    public:
        static inline bool Update()
        {
            return true;
        }

        static inline void Set(float val)
        {
            Parameter<T>() = val;
        }
}

Some explanations: template parameter T is only the value's name. The Update() function is the most interesting. It is static because the value is unique and return a bool stating if the update succeeded. It can return false if some constraints or conditions was not satisfied. It will be recursively called in policies implementation. The Set() function is very simple, it simply assign the value given in parameter. Talk more about Parameter<T>()

template <class T>
float& Parameter(void)
{
  static std::map<std::type_index, float> values;
  return values[typeid(T)];
}

typeid is an operator which is applied on a type to take some information about it on runtime. So, we couple a std::type_index with a float.

We now have to define the recursive case.

template <template Action, class... Actions, class Me>
class Value<Me, Action, Actions...> : public Value<Me, Actions...>
{
    public:
        static inline bool Update()
        {
            float val = Parameter<Me>();
            Action::Update();
            if (!Value<Me, Actions...>::Update())
            {
                Parameter<Me>() = val;
                return false;
            }
            return true;
        }
};

Template parameters are :

  • Me: the value's name, that we have to remember until the end to use it in Parameter<Me>()
  • Action: the current action.
  • Actions...: variadic template parameter, the rest of actions we have to apply

We inherits value parametrized by the rest of actions, basic technique exposed by Andrei Alexandrescu in his typelists usage.

After considerations about templates, the method is not complicated: we save the current value in val, we do the current action, and if following actions performs bad, we can reset the value to val to rollback changes. Define what is an "operation type": it has a static void Modify(float& val, float rate);. val is the value to modify, rate is an amount, and the implementation describes how to to the modification. Example:

struct Plus
{
    static inline void Modify(float& val, float rate)
    {
        val += rate;
    }
};

Now, define the Do class which will wrap everything and give operands:

template <class What, class Method, class Rate>
struct Do
{
    static inline bool Update()
    {
        Method::Modify(Parameter<What>(), Parameter<Rate>());
        return true;
    }
};

Modify changes the value what if function of the implementation of Method::Modify and with the parameter Rate (another value). If we want to harcode Rate with some numbers, we have to define a simple class:

template <int>
class Number{};

And to specialize Do:

template <class What, class Method, int N>
struct Do<What, Method, Number<N>>
{
    static inline bool Update()
    {
        Method::Modify(Parameter<What>(), N);
        return true;
    }
};

Nothing difficult. That's it. Now, all actions just works. We still have to handle conditions.

Define a template condition class : the classe must define a static bool Check(float param1, float param2); Exemple:

struct MoreThan
{
    static bool inline Check(float v1, float v2)
    {
        return v1 < v2;
    }
};

Define the class which will provide the second operand and wrap it in a MustBe class used later for template pattern matching:

template &lt;class Comparator, class T&gt;
struct MustBe
{
    static bool inline Check(float v)
    {
        return Comparator::Check(Parameter<T>(), v);
    }
};

And the specialization for Number

template <class Comparator, int N>
struct MustBe<Comparator, Number<N>>
{
    static bool inline Check(float v)
    {
        return Comparator::Check(N, v);
    }
};

We can now write conditions like MustBe<LessEqThan, Logement> but conditions are still not handled by Value. Using a condition right now will end in a compilation error: compiler will try to match it with the parameter Action, try to execute Action::Update() which does not exist. We have to specialize Value to match the type MustBe<Comparator, Rate>:

template<class T, class Cmp, class... Actions, class Me>
class Value<Me, MustBe<Cmp, T>, Actions...> : public Value<Me, Actions...>
{
    public:
        static inline bool Update()
        {
            if (MustBe<Cmp, T>::Check(Parameter<Me>()))
            {
                return Value<Me, Actions...>::Update();
            }
            else
            {
                std::cout << "Erf, dépendance non satisfaite" << std::endl;
                return false;
            }
        }
};

Here we are. But there is still an issue.

In the beginning I said that the syntaxe won't be that beautiful. To write Value<Lodgement>, Lodgement need to be previously declared. So, use a macro:

#define VALUE(T) class T : public Value<T>{}
#define PARAM(T, V...) class T : public Value<T, V>{}

And write the final declaration in the main():

VALUE(Logement);
VALUE(Budget);
PARAM(Population,
        Do<Population, Plus, Number<50>>,
        MustBe<LessEqThan, Logement>);

Thanks for reading, do not hesitate to comment. I think my solution is not the best but is not the worst, and is a good usage of typeid (T) operator, nice in an article.

Source is avalaible on my bitbucket