防止参数包扩展中的数组衰减

时间:2012-12-17 21:02:32

标签: c++ templates c++11

是否可以防止从参数包扩展的参数中的数组到指针衰减?

例如:

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

...输出:

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

如您所见,第一次调用转到基于数组的重载,但后续调用转到基于指针的重载。 有没有办法让所有调用转到基于数组的重载?

相关:Problems specializing variable template function

2 个答案:

答案 0 :(得分:8)

您希望通过右值参考传递参数包:

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

所以代码整体看起来像这样:

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

给出结果:

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

我没有改变其他重载来做同样的事情,但你通常也希望它们也使用右值引用(如果实际使用它们的话)。

编辑:至于你为什么要这样做/为什么它起作用:右值引用可以绑定到右值或右值。我们关心的关键点是当它与左值结合时,它仍然是左值。在数组的情况下,它将其身份保留为数组,因此收到的是数组。

当/如果我们按值传递数组时,它会经常正常地“衰减”到指针,就像使用普通函数一样。

对于这个特定的情况,我们也可以使用正常的左值引用 - 但是如果我们这样做,那么对任何不是左值的类型都有效。例如,如果我们尝试调用foo(1,2,3);,则会收到错误,因为左值引用无法绑定到123。为了解决这个问题,我们可以传递const左值引用,但是我们不会将引用直接绑定到右值 - 我们将创建一个包含副本的临时副本传递的右值,然后将左值引用绑定到该临时副本。对于int的特定情况,这可能不是一个主要问题,但复制成本更高(或者如果我们想要访问原始文件,而不是副本)可能是一个问题。

答案 1 :(得分:5)

@JerryCoffin的答案已经到了现场,但我想补充一点。您可以将列表处理代码与第一项分开:

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

(也许已经有了成语?)。