C++ multiple template parameter packs

The idea

This is just a short idea for multiple template parameter packs on a specific use case. While there are other more generic ways to achieve this, I found a method that is easier to digest for my case.

One of my learning projects is a map-like container with infinite depth, multiple specific types of keys, and any type of value.

The need for multiple template parameter packs came when I wanted to be more specific about “any type of value”. “Any” is… any. Nothing specific, clear, or well-known. And I wanted more clarity.

My map is declared as:

msd::poly_map<int, double, std::string> map;

The template arguments are the types of keys. No types for the values because the map can hold any value. But I want to be as specific as I am for the keys. What I need is to separate the key types from the value types. I want two sets of template parameters. How could I tell them apart?

The solution

After a few minutes of diving in, the idea that popped up is to store the values exactly how I store the keys: inside a variant. The bonus for switching from any to variant is that:

Variant is not allowed to allocate additional (dynamic) memory.

I introduced an auxiliary type to represent a multiple-parameter pack. And I passed two of these to my map: one for keys, one for values.

template<typename... Types>
struct types {
    using types_ = std::variant<Types...>;
};

template<typename Keys, typename Values>
struct poly_map;

poly_map<types<int, double>, types<int, bool>> map;

The full source code

Everything put together in a raw version looks like:

#include <cassert>
#include <map>
#include <variant>

template<typename... Types>
struct types {
    using types_ = std::variant<Types...>;
};

template<typename... Types>
using keys = types<Types...>;

template<typename... Types>
using values = types<Types...>;

template<typename Keys, typename Values>
struct poly_map {
    std::map<typename Keys::types_, poly_map> items_;

    using value_types = typename Values::types_;
    value_types value_;

    template <typename T>
    auto& operator=(T&& v)
    {
        static_assert(std::is_constructible_v<value_types, T>, "wrong value type");

        value_ = std::forward<T>(v);

        return *this;
    }

    template <typename T>
    auto& operator[](const T key)
    {
        return items_[key];
    }

    template <typename T>
    auto& get() const
    {
        static_assert(std::is_constructible_v<value_types, T>, "wrong value type");

        return std::get<T>(value_);
    }
};

struct X {
    int v{};
};

int main() {
    poly_map<keys<int, double>, values<int, bool, X>> map;

    map[1] = true;
    assert(map[1].get<bool>());

    map[2] = X{1};
    assert(map[2].get<X>().v == 1);
    
    //map[1] = 0.1; // does not compile because map can't hold double as value

    map[1][2.2] = 14;
    assert(map[1][2.2].get<int>() == 14);
}

C++ policy-based design

Removing duplicate code with C++ policy-based design

Lately, I’ve been interested in some C++ template topics that help me design more flexible code. I didn’t explicitly search for them, but I met some cases that led me to find new ways of solving some specific problems.

A case is code duplication. I wanted to find a pattern for some duplicate code. I started with the idea of building components that I can compose however is needed. The first draft was using some lambdas and it was somehow OK. It did the job.

Then, looking around other aspects, I remembered that a friend keeps mentioning policy-based design. I had looked over it before, but I hadn’t found the need for it. But at that moment it clicked.

Let me get into code. I’m showing an oversimplified example because I don’t want to hide the main idea with details. If your real code is just as simple as the one I’m analyzing, do not jump on the idea before considering other approaches.

To give some hints about what follows: static polymorphism, composition, compile-time strategy. There are other approaches (runtime strategy, dynamic decoration), but I wanted to solve this at compile-time.

The goal

Straight forward, I want to remove the duplicate code using C++ policy-based design. It looks complicated at first. Just have patience.

I want to stick to the goal, so I omitted good naming, encapsulation, const correctness, noexcept and other details,

The problematic code

This is the code that has a duplicate piece of code – look for “DUPLICATE LINE” on lines 15 and 27 (for simplicity, it’s just one duplicate line):

#include <cassert>

struct Object {
    int prop_1{};
    int prop_2{};
    int prop_3{};
};

void set_object_properties_1(Object& object)
{
    if (object.prop_1 == 0) {
        object.prop_1 = 1;
    }

    object.prop_2 = object.prop_1 * 4;  // DUPLICATE LINE

    object.prop_3 = object.prop_2 * 5 + object.prop_1 * 2;
}

void set_object_properties_2(Object& object)
{
    if (object.prop_1 == 0) {
        object.prop_1 = 3;
    }
    object.prop_1 *= 2;

    object.prop_2 = object.prop_1 * 4;  // DUPLICATE LINE

    object.prop_3 = object.prop_1 * object.prop_2;
}

int main()
{
    Object object_1{};
    set_object_properties_1(object_1);
    assert(object_1.prop_1 == 1);
    assert(object_1.prop_2 == 4);
    assert(object_1.prop_3 == 22);

    Object object_2{};
    set_object_properties_2(object_2);
    assert(object_2.prop_1 == 6);
    assert(object_2.prop_2 == 24);
    assert(object_2.prop_3 == 144);
}

Continue reading C++ policy-based design

Function overloading and object slicing

A case was pointed out to me where this static iterator (you can skip that article and still understand the current one) would not work as expected, without having a compile or runtime error. Things would appear to work, but from a logical point of view, they could be incorrect.

The iterator can be passed data by several structs, each struct having an overload of a function to operate on it.

struct A;
void operate(input::A& a);

struct B;
void operate(input::B& b);

For each object of these structs, the proper overload of operate is called. But the following case could not work as expected:

struct A;
void operate(input::A& a);

struct B : A;

B inherits from A and there is no operate overload for B. An expectation would be for the code to not compile because there is no function defined to operate on B. But actually, object slicing comes into play: B is a derived class and can be assigned to its base class A. And objects of type B will be operated on with A‘s operate overload. If this is the intended behavior, it’s OK.

If you want to strictly control things and make sure you have defined all the required functions, you need a way to make sure you’re given an error. Ideally a compile-time error. Continue reading Function overloading and object slicing

Abstraction for better APIs

One problem with the iterator used in the static polymorphism article is the lack of abstraction. The storage container is a tuple and the caller is required to explicitly handle it: create it and pass it to the iterator.

This is not ideal because the iterator exposes internal implementation details and its public interface is coupled to those details. A change of the storage container would break the API and the caller would be required to make changes to their code.

Perhaps the tuple could be switched to an array that uses a variant from boost or C++17 standard library. The implementation change should not impact the public API.

This can be achieved by designing an abstract API that does not expose the internal storage. I’m going to present a way of doing this by wrapping the old static iterator (I’ve changed it a little bit; the entire code is at the end of the article) with a new class that will be the public API.

The API accepts a list of types that you want to iterate over by variadic template arguments.

    template <typename... Ts>
    class StaticIterator {
        static_assert(sizeof...(Ts) > 0, "at least one type is required");
    };

Continue reading Abstraction for better APIs

A more decoupled approach on static polymorphism

This is a follow-up of the Executing tasks based on static polymorphism article, which I recommend to be read for the full picture of what is about to come, as it offers details on why I study this approach and how I implemented it (compile-time iteration of a tuple).

My first attempt on C++ compile-time polymorphism is designed around a task struct. The requirement for a task is to implement an Execute method that will perform some work. This requires that the task struct is mine. Otherwise, if there’s some information provided by another library through some struct, I can wrap it in a task that has the required Execute method.

Inspired by some of Sean Parent’s talks about runtime polymorphism and some of its issues, I found another way of implementing static polymorphism. One that does not have any requirement for the input structs; they don’t need to implement a method nor they must be wrapped in other structs.

Along with the requirements in the previous article, I add these ones:

    • A container with multiple objects of different types so I have a list of items
    • For each object in the container, something different must be performed depending on its type (by iterating all objects, not handling it manually)
    • Objects in the container are provided by someone else and cannot be changed
    • C++11/14 compatible

This approach starts with some input structs in a tuple (the container); references to the objects constructed of the structs to prevent copies:

namespace input {
    struct A {
        int a;
    };

    struct B {
        int b;
    };
}

using Objects = std::tuple<input::A&, input::A&, input::B&, input::A&>;

Continue reading A more decoupled approach on static polymorphism

Executing tasks based on static polymorphism

If you want to skip reading and get to the code, then see some kind of C++ static polymorphism in the GitHub repository.

I’m a fan of tasks. I enjoy each time I implement any kind of system that executes tasks, from very simple ones (based on a list of tasks executed on a single thread) to multithreaded ones with features like cancellation and dynamic concurrency level.

My first encounter with the idea of having a list of objects that I iterate and call a function for each one of them was when I had to implement a feature that dynamically restricted users’ access to some content based on various conditions. You can think about a lot of if statements. I don’t like if statements, I struggle to avoid them because each one of them brings new responsibility, test cases, and maintenance costs. And I really enjoy seeing product and management people happy when their requirements are implemented at reasonable times. And being productive is a lot about code.

A way to decouple code is, of course, to separate concerns. And if you’re in a case with multiple concerns serving the same concept, you could use a polymorphic approach. You can have an interface for a task, some tasks implementations, and something to run all the tasks when needed. Everything you’ll see in this article is a light version because I’m focusing on the idea, not on details.

#include <array>
#include <iostream>
#include <memory>

class Task {
   public:
    virtual void Execute() = 0;
};

template <typename T>
struct Executor {
    T& tasks;

    void Execute()
    {
        for (auto& task : tasks) {
            task->Execute();
        }
    }
};

class A : public Task {
   public:
    void Execute() override { std::cout << 1; }
};

class B : public Task {
   public:
    void Execute() override { std::cout << 2; }
};

int main()
{
    using Tasks = std::array<std::unique_ptr<Task>, 2>;
    Tasks tasks{
        std::make_unique<A>(),
        std::make_unique<B>(),
    };

    Executor<Tasks> executor{tasks};
    executor.Execute();
}

 

Now I’ll add some constraints for learning purposes. Sometimes performance matters and you can’t always use the latest C++ standards, so I’ll go with these:

    • C++ 11
    • No heap allocations
    • Minimum stack allocations
    • No virtual methods

And I’m entering the world of templates because this can help me achieve compile-time polymorphism (C++ static polymorphism), and that’s what I’m aiming for. Continue reading Executing tasks based on static polymorphism