Archiwum | C++14 RSS for this section

Creating service container using variadic templates

Let’s have two simple services:

struct T1{
void process(){
cout << "processing T1" << endl;
}
};

struct T2 {
void process() {
cout << "processing T2" << endl;
}
};

Note, they share similar interface. But for compiler these structs are completely unrelated.

Lets say, we want to create a container, which will store our services, and allows to call each service methods.

We want something like this:

auto con = make_container(T1{},T1{}, T2{});
con.process();

>processing T1
>processing T1
>processing T2

 

Of course this could be done by adding a base abstract class which define a common interface for our services. Next, a container – vector for example – for storing (smart) pointers to services. And finally, a class that encapsulate all.

struct storage {
 void process(){
  for (auto &base : container)
    base->process();
 }

private:

std::vector<std::unique_ptr<ServiceBase>> container;
};

This solution is perfectly fine, but:

Sometimes services are defined at compile time, and will not be changed during runtime. Vector’s runtime capabilities are unnecessary.

Services are allocated separately on heap, which may not be cache-friendly.

Solution:


template<typename ...Args>
struct Container{};

template <>
struct Container<>{

    void process(){
    }
};

template<typename T, typename ...Args>
struct Container<T, Args...> {

    Container(T _value, Args ... a) :
        value(_value), childs(a...)
    {}

    void process(){
        value.process();
        childs.process();
    }

    T value;
    Container<Args...> childs;
};

template<typename ...Args>
Container<Args...> make_container(Args ... args){
    return Container<Args...>(args...);
}

This code was inspired by some std::tuple implementations as saw recently.

Instead of recursive inheritance generation, which is used in tuple, this code recursively generates type of „childs” member.

After all variadic magic, compiler generated something like this:

(Pseudocode)

struct Container1{
void process(){}; // empty
}

struct Container2{
  void process() {
     // do my stuff
     childs.process();
  };
  Container1 childs;
}

struct Container3{
  void process() {
     // do my stuff
     childs.process();
  };
  Container2 childs;
}

And more details (pseudocode):

template<typename T>
struct Container1{
void process(){
  value.process();
};
T value;
}

template<typename T, typename C>
struct Container2{
  void process() {
     value.process();
     childs.process();
  };
  Container1<C> childs;
  T value;
}

template<typename T, typename C>
struct Container3{
  void process() {
     value.process();
     childs.process();
  };
  Container2<C> childs;
  T value;
}

void test(){
  auto container3 = create_container3(....);
  container3.process();
}

The really cool thing in this solution is its cache friendliness. All of the data of these structures are located close to each other.

If services implements copy constructors – container3 can be copied.

As long as services share same interface – container can hold any (well, reasonable) number of them, regardless of type.

Happy coding