How to pack variadic arguments of a template function into an array?

176 Views Asked by At

How to pack variadic arguments of the function into an array and return it from that function? All the arguments are of the same type.

Here's what I try to do, but it only seems to set the very first element of the array and rest of the elements are zero. So that's no good.

template<typename... T>
inline auto CreateArray(const T... t)
{
    constexpr int ELEMENT_COUNT = TotalElementCount(t...);

    return std::array<int, ELEMENT_COUNT>{ (t, ...) };
}

So if I run the following code:

    auto new_array = CreateArray(1, 2, 3, 4);

    for (auto x : new_array)
        std::cout << std::dec << x << std::endl;

I'll get the result:

4
0
0
0

What's wrong with my code?

2

There are 2 best solutions below

0
On BEST ANSWER
  • TotalElementCount(t...); should be just sizeof...(t) and the result is not an int but a std::size_t. You can use auto to get the correct type automatically.
  • (t, ...) is a fold over the comma operator. The comma operator discards results of the left operands and leaves only the rightmost operand. Compare with:
    std::cout << (1,2,3,4) << '\n'; // prints 4
    

What you want here is not a fold expression but a pack expansion:

template <typename... T>
inline auto CreateArray(const T... t) {
    constexpr auto ELEMENT_COUNT = sizeof...(t);

    return std::array<int, ELEMENT_COUNT>{t...};
//                                       ^^^^^^
}

A perfect forwarding version:

template <typename... T>
inline auto CreateArray(T&&... t) {
    constexpr auto ELEMENT_COUNT = sizeof...(t);

    return std::array<int, ELEMENT_COUNT>{std::forward<T>(t)...};
}

You may also want to replace the value type int in the array with std::common_type_t<T...> to allow for the creation of non-int arrays.

0
On

As indicated in the commands, the fix for your original question is simple,

return std::array{t...};

The fix for your (now removed) "additional detail" is also basically just the same. However, as I said in the comment, you really need to understand the difference between these two very similar syntax to understand what is wrong.

First, you need to understand that the (unquoted, of course) comma in C++ has two major roles: as a separator, and as an operator. The comma operator in a,b means evaluate a first, then evaluate b. It is not used very often and you should be relatively easy to tell it when you meet one. Most of the comma you see are separators. This include the commas in parameter and argument lists (f(int,int) , f(a,b), template <typename, typename>, f<1,2>() ), as well as braced-init-lists (std::array{1,2,3}).

The correct syntax here, std::array{t...}, is called pack expansion and is introduced in C++11, it expands an expression to a list of expressions separated by the comma separator. For example, if t is a pack containing t1 and t2, f(t)... expands to f(t1),f(t2), while t(f) expands to t1(f),t2(f). The commas here are separators. Pack expansion is what you need most of the time.

That means in all the cases mentioned as examples for comma separators, you should use pack expansion,

void f(T...); //declaration of variadic function
f(t...); //calling variadic function
template <typename...> class C{}; //template class with variadic type parameters
f<t...>(); //Calling function with explicit variadic template parameters
std::array{t...}; //Braced-init-list

and many more.

Your syntax here, (t,...) used as an expression, is a fold expression introduced in C++17. For the same t as the previous example, It expands to (t1, t2), where the comma is an operator, meaning t1 gets evaluated, and then t2, but in the end, you only get a single expression, unlike a list of expressions in the previous case. If you are not familiar with template meta programming, you seldom need to use fold expression with the comma separator. This type of expression is often used for tricks where the side-effect of each expression is of major interest. An example is:

(logging_func(t),...)

This will log each parameter in the parameter pack. The final result of the expression is discarded.

It is now very clear that what you need is pack expansion (which is also what you need in most cases) not fold expression, and the fix is presented above.