Search code examples
c++c++11c++14variadic-templatesc++17

C++ parameter pack with single type enforced in arguments


I want to be able to do the following:

#include <array>
struct blah { };

template<typename... Args>
constexpr auto foo(Args&&... args)
{
    return std::array<blah, sizeof...(Args)>{{ args... }};
}

auto res = foo({}, {});

The following answers aren't satisfying: they just want to check that the parameter pack is of a single type, but I want to convert the values right to it in the arguments (else it does not work).

C++ parameter pack, constrained to have instances of a single type?

Parameter with non-deduced type after parameter pack

Specifying one type for all arguments passed to variadic function or variadic template function w/out using array, vector, structs, etc?

I also can't use initializer_list since I wouldn't be able to count the number of arguments to pass to the array type. And I especially don't want to type foo(blah{}, blah{});.

What are my possibilities ?


Solution

  • A little bit expanded approach of Jarod42 for lazies (C++17):

    #include <utility>
    #include <array>
    
    struct blah {};
    
    template <class T, std::size_t I>
    using typer = T;
    
    template <class T, std::size_t N, class = std::make_index_sequence<N>>
    struct bar_impl;
    
    template <class T, std::size_t N, std::size_t... Is>
    struct bar_impl<T, N, std::index_sequence<Is...>> {
        static auto foo(typer<T, Is>... ts) {
            return std::array<T, N>{{ts...}};
        }
    };
    
    template <class T = blah, std::size_t N = 10, class = std::make_index_sequence<N>>
    struct bar;
    
    template <class T, std::size_t N, std::size_t... Is>
    struct bar<T, N, std::index_sequence<Is...>>: bar_impl<T, Is>... {
        using bar_impl<T, Is>::foo...;
    };
    
    int main() {
        bar<>::foo({}, {});
    }
    

    [live demo]

    Edit:

    Some C++14 solution which (as noted by max66) is even simpler than I expected:

    #include <utility>
    #include <array>
    
    struct blah {};
    
    template <class T, std::size_t I>
    using typer = T;
    
    template <class T = blah, std::size_t N = 10, class = std::make_index_sequence<N>>
    struct bar;
    
    template <class T, std::size_t N, std::size_t... Is>
    struct bar<T, N, std::index_sequence<Is...>>: bar<T, N - 1> {
        using bar<T, N - 1>::foo;
        static auto foo(typer<T, Is>... ts) {
            return std::array<T, N>{{ts...}};
        }
    };
    
    template <class T>
    struct bar<T, 0, std::index_sequence<>> {
        static auto foo() {
            return std::array<T, 0>{{}};
        }
    };
    
    int main() {
        bar<>::foo({}, {});
    }
    

    [live demo]

    One more edit:

    This one (as suggested by Jarod42) provides exactly the same syntax for invocation as in OP's question:

    #include <utility>
    #include <array>
    
    struct blah {};
    
    template <class T, std::size_t I>
    using typer = T;
    
    template <class T = blah, std::size_t N = 10, class = std::make_index_sequence<N>>
    struct bar;
    
    template <class T, std::size_t N, std::size_t... Is>
    struct bar<T, N, std::index_sequence<Is...>>: bar<T, N - 1> {
        using bar<T, N - 1>::operator();
        auto operator()(typer<T, Is>... ts) {
            return std::array<T, N>{{ts...}};
        }
    };
    
    template <class T>
    struct bar<T, 0, std::index_sequence<>> {
        auto operator()() {
            return std::array<T, 0>{{}};
        }
    };
    
    bar<> foo;
    
    int main() {
        foo({}, {});
    }
    

    [live demo]