Search code examples
c++templatesc++11variadic-templates

How to handle a case of empty parameter pack


I have the following code that I use to get size of all primitive types in the pack (I treat floats and doubles in a special way), but my program fails to compile when the pack is empty.

// terminating general case for T being anything else
template <class T>
size_t getSize()
{
    return sizeof(T);
}

// terminating special case for T=double
template<>
size_t getSize<double>()
{
    return SIZEOF_DOUBLE;
}

// terminating special case for T=float
template<>
size_t getSize<float>()
{
    return SIZEOF_FLOAT;
}

// recursive case
template <class T, class U, class ...S>
size_t getSize()
{
    return getSize<T>() + getSize<U, S...>();
}

I want getSize to return 0 when called like

template <class ...T>
void foo(T... arg)
{
    size_t sizeOfTypes = getSize<T...>();
}

with T={}, i.e. foo is called like foo<>();.

Note that I don't want to modify the way the foo is called, the angle brackets have to stay there. Preferably I'd like to have getSize modified, because I use it in several functions other than foo. Modify foo as the last resort.


Solution

  • Another approach to solve this would be to use a couple of little structs like

    template<typename T>
    struct GetTypeSize
    {
        enum { value = sizeof(T) };
    };
    
    template<>
    struct GetTypeSize<float>
    {
        enum { value = SIZEOF_FLOAT };
    };
    
    template<>
    struct GetTypeSize<double>
    {
        enum { value = SIZEOF_DOUBLE };
    };
    
    template<typename...>
    struct GetSize 
    {
        enum { value = 0 };
    };
    
    template<typename Head, typename... Tail>
    struct GetSize<Head, Tail...>
    {
        enum { value = GetTypeSize<Head>::value + GetSize<Tail...>::value };
    };
    
    template<typename... T>
    void foo(T... arg)
    {
        size_t sizeOfTypes = GetSize<T...>::value;
    }
    

    This has the advantage of beeing evaluated (summed up) during compile time.

    I used two types of structs. One for doing the recursion (GetSize) and another for getting the actual size of the types (GetTypeSize). The specialization of GetSize<Head, Tail...> is instantiated as long as there is a head (the pack is not empty) and adds the size of the type in Head to the recursive call of GetSize<Tail...>. Once there is no Head the fallback GetSize template is used.

    For an instantiation of GetSize<int, double, char> it results in

    GetTypeSize<int>::value + GetTypeSize<double>::value + GetTypeSize<char>::value + GetSize<>::value

    which then is

    sizeof(int) + SIZEOF_DOUBLE + sizeof(char) + 0