'Variadic template packed argument to std::vector

I'm new to templates and I don't really undestand why this doesn't work. I expected the vector to be constructed with those values.

main.cpp


template <typename ...T>
void int_printf(T ...args)
{
    std::vector<T> vec = {args...};

    for(auto& v:vec)
    {
        std::cout << v << std::endl;
    }
}

int main()
{
    int_printf(1,2,3,4);

    return 0;
}

Expected result

1
2
3
4

Error by msvc compiler (translated)

src/main.cpp(35): error C3520: 'T': the parameter pack must be expanded in this context
src/main.cpp(37): error C3536: '<begin>$L0': can't be used before initialization
src/main.cpp(37): error C3536: '<end>$L0': can't be used before initialization
src/main.cpp(37): error C2100: invalid redirection


Solution 1:[1]

Another, slightly more wordy, way to do this is to add an initial template parameter specifying the type of vec, like so:

#include <iostream>
#include <vector>

template <typename T, typename ... Args>
void int_printf(Args ... args)
{
    std::vector<T> vec = {args...};

    for (auto& v : vec)
    {
        std::cout << v << std::endl;
    }
}

int main()
{
    int_printf<int>(1,2,3,4);
    return 0;
}

This might give clearer error messages if you pass a list of incompatible types to int_printf.

Solution 2:[2]

The issue in your code, is that T is not a template parameter in this context, it is a template parameter pack, which would expand to T=[int,int,int,int] in your example. std::vector expects a type to be passed as a template parameter, not a template parameter pack. You can solve this issue by using std::common_type:

#include<type_traits>

template <typename ...T>
void int_printf(T ...args)
{
    //use std::common_type to deduce common type from template
    //   parameter pack
    std::vector<typename std::common_type<T...>::type> vec = {args...};

    for(auto& v:vec)
    {
        std::cout << v << std::endl;
    }
}

You should note, this will only work if the arguments passed to int_printf have a common type.

Solution 3:[3]

When you do std::vector<T>, the T is not a single type but is instead is a pack of types. You can't use that for the vector because it wants a single type for the element.

There is a couple ways to handle this. First would be to just hard code the type of the vector. This makes the code less generic, but would work for you since your function is called int_printf and not anything_print

Another option is to use std::common_type to get the common type of the elements like

template <typename ...T>
void int_printf(T ...args)
{
    std::vector<std::common_type_t<T...>> vec = {args...};

    for(auto& v:vec)
    {
        std::cout << v << std::endl;
    }
}

You could also use a fold expression and skip the vector entirely like

template <typename ...T>
void int_printf(T ...args)
{
    ((std::cout << args << std::endl), ...);
//  ^^                                    ^
//  |          do this part         ^  ^  |
//  |              for each parameter  |  |
//  start fold expression          end fold
}

If you want just an unlimited number of int's you could also use SFINAE to constrain the pack type to be integers like

template <typename ...T, std::enable_if_t<std::conjunction_v<std::is_same<T, int>...>, bool> = true>
void int_printf(T ...args)
{
    ((std::cout << args << std::endl), ...);
}

and now you can't call this function with anything other than int's but it can have as many as you want.

Solution 4:[4]

You can use a std::variant in conjunction with std::vector. Here is an example:

template<typename... Args>
class VariantTest
{
private:
    using ArgTypes = std::variant<Args...>;
    std::vector<ArgTypes> elems;
public:
    VariantTest()
    {
        elems.reserve(10); //just a number
    }

    template<typename... ArgsL>
    void AddTypes(ArgsL&&... args)
    {
        (elems.emplace_back(std::forward<ArgsL>(args)), ...);
    }
    size_t GetElemsCount()
    {
        return elems.size();
    }
};

int main()
{

    VariantTest<A, B> vt;
    vt.AddTypes(B(), A()); //Note the order does not matter.
    std::cout << "Number of elements: " << vt.GetElemsCount() << '\n';
    return 0;
}

You will need C++17.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2
Solution 3
Solution 4 user14581033