Search code examples
c++11templatesvariadic-templatestemplate-specialization

How can I "generate" template spezialations for a function from a variadic template argument?


I think I will first show the example and then explain it:

#include <array>

template<typename T_, size_t size_>
struct arg
{
    using T = T_;
    static constexpr size_t size = size_;
};

template<typename... Arugments>
struct Foo
{
    template<typename Argument>
    std::array<typename Argument::T, Argument::size>& getArray() // specializations of all args in Arguments should be generated
    {
        static std::array<typename Argument::T, Argument::size> arr;
        return arr;
    }
};

int main()
{
    Foo<arg<int, 10>, arg<float, 10>, arg<float, 1>> myFoo;
    myFoo.getArray<arg<int, 10>>();
    myFoo.getArray<arg<float, 10>>(); // should return a different array than the line above
    myFoo.getArray<arg<bool, 1>>(); // should NOT work because arg<bool, 10> is was not passed to Foo
}

If got a struct arg which contains the information how to construct arr in getArray. A list of args is passed to Foo. Now I want that template specializations of getArray to be generated for each arg in Arguments. If no specialization was generated for a specific arg I want that some kind of error occurs.

How could I achieve this?


Solution

  • You can use static_assert to make sure Argument is part of Arguments with a helper struct.

    #include <array>
    #include <iostream>
    
    template <typename... T>
    struct contains;
    
    template <typename T>
    struct contains<T> : std::false_type {};
    
    template <typename T, typename U, typename... Rest>
    struct contains<T, U, Rest...> : contains<T, Rest...>  {};
    
    template <typename T, typename... Rest>
    struct contains<T, T, Rest...> : std::true_type {};
    
    template<typename T_, std::size_t size_>
    struct arg
    {
        using T = T_;
        static constexpr std::size_t size = size_;
    };
    
    template<typename... Arguments>
    struct Foo
    {
        template<typename Argument>
        std::array<typename Argument::T, Argument::size>& getArray() // specializations of all args in Arguments should be generated
        {
            static_assert(contains<Argument, Arguments...>(), "Invalid type");
            static std::array<typename Argument::T, Argument::size> arr;
            return arr;
        }
    };
    
    int main()
    {
        Foo<arg<int, 10>, arg<float, 10>, arg<float, 1>> myFoo;
        myFoo.getArray<arg<int, 10>>()[5] = 7;
        myFoo.getArray<arg<float, 10>>(); // should return a different array than the line above
        //myFoo.getArray<arg<bool, 1>>(); // should NOT work because arg<bool, 10> is was not passed to Foo
    
        Foo<arg<int, 10>, arg<float, 10>, arg<float, 1>> myFoo2;
        std::cout << myFoo2.getArray<arg<int, 10>>()[5];
    
        Foo<arg<int, 10>, arg<float, 10>, arg<double, 1>> myFoo3;
        std::cout << myFoo3.getArray<arg<int, 10>>()[5];
    }
    

    Just wanted to also point out that as demonstrated by the code, myFoo and myFoo2 returns the same array since they are the exact same type.

    myFoo3 on the other hand is a separate type, which means the getArray member function is a separate function and has it's own copy of the same type array.