Is it possible to prevent array-to-pointer decay in arguments expanded from a parameter pack?
For example:
#include <iostream>
void foo() {
std::cout << "empty\n";
}
template <typename T, typename... Rest>
void foo(T &&t, Rest... rest) {
std::cout << "T, ...\n";
foo(rest...);
}
template <typename... Rest>
void foo(char *p, Rest... rest) {
std::cout << "char*, ...\n";
foo(rest...);
}
template <int N, typename... Rest>
void foo(char (&first)[N], Rest... rest) {
std::cout << "char[], ...\n";
foo(rest...);
}
int main() {
char a[2], b[2], c[2];
foo(a, b, c);
}
…outputs:
char[], ...
char*, ...
char*, ...
empty
As you can see, the first call goes to the array-based overload, but subsequent calls go to the pointer-based overload. Is there any way to get all of the calls to go to the array-based overload?
You want to pass the parameter pack by rvalue reference:
Although it’s not exactly related to the question at hand, when you forward arguments, you also want to use
std::forward. So with those modifications, the code looks something like this overall:Giving the result:
I haven’t changed the other overloads to do the same, but you’d normally want them to use an rvalue reference as well (if they were actually being used).
As to why you’d want to do this/why it works: an rvalue reference template parameter can bind to either an rvalue or an lvalue. The crucial point we care about here is that when it binds to an lvalue, it remains an lvalue. In the case of an array, it retains its identity as an array, so what’s received is an array.
When/if we pass an array by value, it undergoes the normal "decay" to a pointer, just like with a normal function.
For this specific case, we could also use a normal lvalue reference — but if we did, that would not work for any type that wasn’t an lvalue. For example, if we tried to call
foo(1,2,3);, we’d get an error because an lvalue reference can’t bind to1,2or3. To deal with that we could pass aconstlvalue reference, but then we wouldn’t be binding the reference directly to the rvalue — we’d be creating a temporary containing a copy of the rvalue that was passed, and then binding the lvalue reference to that temporary copy instead. For the specific case of an int, that probably wouldn’t be a major problem, but with something that was more expensive to copy (or if we wanted access to the original, not a copy) that could be a problem.