是否可以防止从参数包扩展的参数中的数组到指针衰减?
例如:
#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
如您所见,第一次调用转到基于数组的重载,但后续调用转到基于指针的重载。 有没有办法让所有调用转到基于数组的重载?
答案 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);
,则会收到错误,因为左值引用无法绑定到1
,2
或3
。为了解决这个问题,我们可以传递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...
(也许已经有了成语?)。