Search code examples
c++templatesvariadic-templatestemplate-meta-programming

How to avoid duplicated code when using recursive parameter packs C++


How do you avoid code duplication when using varadic parameters in c++? Notice that I'm using templates recursively to achieve my goals, therefore I need some base cases and a recursive case. This creates a lot of code duplication, are there ways I could reduce this duplication?

Below, an example is provided of code that creates an arbitrary tensor (N dimensional array).

It's working fine but there's too much duplication. How can I avoid writing duplicated code when using template parameter packs recursively like this?

#include <cstddef>
#include <array>
#include <iostream>

template<typename T, std::size_t...>
class Tensor;

template<typename T, std::size_t N>
class Tensor<T, N> {
    using Type = std::array<T, N>;
    Type data;
public:

    Tensor()
    {
        zero();
    }

    void zero()
    {
        fill(0);
    }

    Type::iterator begin() { return data.begin(); }
    Type::iterator end() { return data.end(); }

    void fill(T value)
    {
        std::fill(data.begin(), data.end(), value);
    }

    void print() const
    {
        std::cout << "[";
        for(const auto& v : data)
        {
            std::cout << v << ",";
        }
        std::cout << "]";
    }
};

template<typename T, std::size_t N, std::size_t M>
class Tensor<T, N, M>
{
    using Type = std::array<Tensor<T, M>, N>;
    Type data;
public:

    Tensor()
    {
        zero();
    }

    void zero()
    {
        fill(0);
    }

    Type::iterator begin() { return data.begin(); }
    Type::iterator end() { return data.end(); }

    void fill(T value)
    {
        for(auto& v: data) {
            std::fill(v.begin(), v.end(), value);
        }
    }

    void print() const
    {
        std::cout << "[";
        for(const auto& v : data)
        {
            v.print();
            std::cout << ",";
        }
        std::cout << "]";
    }
};

template<typename T, std::size_t N, std::size_t... M>
class Tensor<T, N, M...>
{
    using Type = std::array<Tensor<T, M...>, N>;
    Type data;
public:
    Type::iterator begin() { return data.begin(); }
    Type::iterator end() { return data.end(); }

    Tensor()
    {
        zero();
    }

    void zero()
    {
        fill(0);
    }

    void fill(T value)
    {
        for(auto& v: data) {
            v.fill(value);
        }
    }

    void print() const
    {
        std::cout << "[";
        for(const auto& v : data)
        {
            v.print();
            std::cout << ",";
        }
        std::cout << "]";
    }

};

Solution

  • The only difference between a single-dimension tensor and a multiple-dimension tensor is the type of std::array, T for single and Tensor<T, M...> for another.

    template<typename T, std::size_t N, std::size_t... M>
    class Tensor<T, N, M...> {
        using InnerT = std::conditional_t<(sizeof...(M) > 0),
                                    Tensor<T, M...>,
                                    T>;
        using Type = std::array<InnerT, N>;
        Type data;
    }
    

    Then, use if constexpr to distinguish single-dimension case,

        void fill(T value)
        {
            if constexpr(sizeof...(M) > 0) {
                for(auto& v: data) {
                    v.fill(value);
                }
            } else {
                std::fill(data.begin(), data.end(), value);
            }
        }
    
        void print() const
        {
            std::cout << "[";
            for(const auto& v : data)
            {
                if constexpr(sizeof...(M) > 0) {
                    v.print();
                    std::cout << ",";
                } else {
                    std::cout << v << ",";
                }
            }
            std::cout << "]";
        }
    

    Demo