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

How to write generic version of variadic template class that calculates sum of integers


I came across the following code (below) on this site

https://www.modernescpp.com/index.php/c-insights-variadic-templates

But this declaration/definition works only with integers and I want to write a version that works with other types like float, double, std::strings and user defined types that overloads "+" operator. However I am struggling to write one.

Please note that the mentioned site has variadic function template based solution that works with different datatypes including floating point types(not tried with user defined types yet). I am looking for a variadic template class based solution. This is purely for learning purpose.

Can someone please help me to get this right?

#include<iostream>

template<int...>
struct add;

template<>
struct add<>
{
  static constexpr int value = 0;
};

template<int i, int... tail>
struct add<i, tail...>
{
  static constexpr int value = i + add<tail...>::value;
};

int main()
{
    std::cout << add<1,2,3,4>::value;
}

I wrote this but got stuck

template<typename ...>
struct add;

template<typename T, typename... args>
struct add<T, args...>
{
    static constexpr T value = ??//T();//what to write here?
};

Thanks in Advance.


Solution

  • It is also possible to do so for non integer types with some modification!

    Templates non-type, non-template parameters should be of integer types or of reference/pointer with a linkage or some limited more possibilities. One can read the full list here Template parameters and template arguments.

    Since floating types can not appear as templates non-type, non-template parameters/arguments, the best next option is to take them by reference.

    So the struct becomes like:

    template<auto& ...>
    struct add{
        static constexpr auto value = 0;
    };
    template<auto& first, auto& ... others>
    struct add<first, others...>{
        static constexpr auto value = first + add<others ...>::value;
    };
    

    Values should be stored as constants (with a linkage) first, so before main():

    const auto v1 = 12; //int
    const auto v2 = 54L; //long
    const auto v3 = 3.25242; //double
    const auto v4 = 75.7256L; //long double
    

    Then they can be used any where:

    #include <iostream>
    int main(){
        std::cout << add<v1, v2, v3, v4>::value << std::endl;
    }
    

    Possible output:

    144.978
    

    It works not only with (mixed) integer types and (mixed) floating types but also any custom type provided that the custom types satisfy specific properties including having constexpr constructors and operator +. It also has to have some sort of type conversion operator or other means to achieve similar functionality. For example this type can be used:

    class custom_type{
        const float v;
        //this one works too but the first is better for the purpose.
        //float v;
    public:
        template<typename T>
        constexpr custom_type(T v_):v(v_){}
        template<typename T>
        constexpr auto operator +(T o)const{
            return o + 7345 + v ;
        }
        //this one works but the next one is  better for the purpose.
        //operator auto()const{
        //this one works too but the next one is more clear.
        //constexpr operator auto()const{
        template<typename T>
        constexpr operator T()const{
            return v;
        }
    };
    

    Putting it all together:

    template<auto& ...>
    struct add{
        static constexpr auto value = 0;
    };
    template<auto& first, auto& ... others>
    struct add<first, others...>{
        static constexpr auto value = first + add<others ...>::value;
    };
    
    class custom_type{
        const float v;
    public:
        template<typename T>
        constexpr custom_type(T v_):v(v_){}
        template<typename T>
        constexpr auto operator +(T o)const{
            return o + 7345 + v ;
        }
        template<typename T>
        constexpr operator T()const{
            return v;
        }
    };
    
    const auto v1 = 12; //int
    const auto v2 = 54L; //long
    const auto v3 = 3.25242; //double
    const auto v4 = 75.7256L; //long double
    const custom_type v5 = 34.234; //custom_type
    
    #include <iostream>
    int main(){
        std::cout << add<v1, v2, v3, v4, v5>::value << std::endl;
    }
    

    Possible output:

    7524.21
    

    Please note the struct add for C++ versions lower than 17 can only take arguments of a single type and for the type double would be like:

    template<const double& ...>
    struct add{
        static constexpr double value = 0;
    };
    template<const double& first, const double& ... others>
    struct add<first, others...>{
        static constexpr double value = first + add<others ...>::value;
    };
    

    And constants:

    const double v1 = 12;
    const double v2 = 54L;
    const double v3 = 3.25242;
    const double v4 = 75.7256l;
    

    Good luck!