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

prevent array decay in parameter pack expansion

本文关键字:数组 衰减 扩展 参数 包扩展      更新时间:2023-10-16

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

例如:

#include <iostream>
void foo() {
std::cout << "emptyn";
}
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

正如您所看到的,第一个调用转到基于数组的重载,但随后的调用转到基于指针的重载有没有办法让所有调用都转到基于数组的重载

相关:专业化可变模板函数的问题

您想要通过右值引用传递参数包:

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

尽管它与手头的问题并不完全相关,但当您提出论点时,您也希望使用std::forward。因此,经过这些修改,代码总体上看起来是这样的:

#include <iostream>
void foo()
{
std::cout << "emptyn";
}
template <typename T, typename... Rest>
void foo(T&& t, Rest... rest)
{
std::cout << "T, ...n";
foo(std::forward<Rest>(rest)...);
}
template <typename... Rest>
void foo(char const* p, Rest... rest)
{
std::cout << "char*, ...n";
foo(std::forward<Rest>(rest)...);
}
template <int N, typename... Rest>
void foo(char (&first)[N], Rest&&... rest)
{
std::cout << "char[], ...n";
foo(std::forward<Rest>(rest)...);
}
int main()
{
char a[2], b[2], c[2];
foo(a, b, c);
}

给出结果:

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

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

至于为什么要这样做/为什么它有效:右值引用模板参数可以绑定到右值或左值。我们在这里关心的关键点是,当它与左值绑定时,它仍然是左值。在数组的情况下,它保留其作为数组的身份,因此接收到的是数组。

当/如果我们按值传递数组时,它会经历正常的"0";衰变;指向指针,就像使用普通函数一样。

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

@JerryCoffin的回答已经切中要害,但我想补充一句话。您可以将列表处理代码从项目中分离出来,如下所示:

void foo_list() {
std::cout << "emptyn";
}
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...

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