Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

prevent array decay in parameter pack expansion

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?

Related: Problems specializing variable template function

like image 246
Nate Kohl Avatar asked Dec 17 '12 21:12

Nate Kohl


People also ask

How do you prevent array decay?

To prevent array decay Array decay is prevented by passing the size of array as a parameter and do not use sizeof() on parameters of array. Pass the array into the function by reference. It prevents the conversion of array into pointer and it prevents array decay.

What is STD decay?

std::decay Applies lvalue-to-rvalue, array-to-pointer, and function-to-pointer implicit conversions to the type T , removes cv-qualifiers, and defines the resulting type as the member typedef type . Formally: If T names the type "array of U " or "reference to array of U ", the member typedef type is U*.


2 Answers

You want to pass the parameter pack by rvalue reference:

void foo(char (&first)[N], Rest&&... rest)
                               ^^

So the code looks like this overall:

#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);
}

Giving the result:

char[], ...
char[], ...
char[], ...
empty

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).

Edit: As to why you'd want to do this/why it works: an rvalue reference 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 to 1, 2 or 3. To deal with that we could pass a const lvalue 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.

like image 131
Jerry Coffin Avatar answered Oct 26 '22 00:10

Jerry Coffin


@JerryCoffin's answer hit the spot already, but I wanted add a small remark. You can separate the list processing code from the item one like this:

void foo_list() {
   std::cout << "empty\n";
}

template <typename T, typename... Rest>
void foo_list(T &&t, Rest&&... rest) {
  foo(t);
  foo_list(rest...);
}

template <int N>
void foo(char (&t)[N]){
   // ...
}

void foo(char *){
   // ...
}

// etc...

(Maybe there's already an idiom for that?).

like image 35
didierc Avatar answered Oct 26 '22 00:10

didierc