我想从函数返回一个数组(或对数组的引用),如下所示:
decltype(auto) bar() { static int a[2]; return a; }
不幸的是,它会导致非常隐晦的错误。GCC抱怨:error: cannot convert 'int [2]' to 'int [2]' in return
和Clang也不能更好地解释这个问题:
error: array initializer must be an initializer list
演示:https://gcc.godbolt.org/z/ao7Txa9oP
是否有可能从函数返回一个数组?(我很高兴摆脱decltype(auto)
.)
如果不是,那么为什么,假设函数可以以各种方式接受数组,例如:
void f(auto [2]);
void f(auto (&)[2]);
问题确实是decltype(auto)
。通过以下列表推导返回类型(所讨论的表达式是给定给return语句的表达式):
[dcl.type.decltype] (为强调而修改)
decltype-specifier: decltype ( expression )
1对于表达式E,定义了用decltype(E)表示的类型如下:
- …
- …
- 否则,如果E是未加括号的id表达式或未加括号的类成员访问([expr.ref]),则decltype(E)是E所命名的实体的类型,如果没有这样的实体,或者如果E命名了一组重载函数,程序是病态的;
- …
- …
a
类型为int[2]
。所以你本质上定义了一个数组返回类型的函数…但是…
[dcl.fct]
函数的返回类型不能为数组或函数,尽管它们可能具有类型指针或返回类型对这类事物的参考不应该有函数数组,虽然可以有指向函数的指针数组。
您无意中生成了一个格式不良的函数类型。所以是的,解决方案,正如你所指出的,是不使用decltype
。您可以显式地返回一个引用(指向推导出的数组类型),因为这正是您真正想要的:
auto& bar() { static int a[2]; return a; }
或者如果你想明确返回类型,而不需要进入声明器地狱,你可以在末尾指定它:
auto bar() -> int(&)[2] { static int a[2]; return a; }
无论哪种方式,我认为这比依赖decltype
的(有时是模糊的)语义要好。当你显式地返回一个左值引用时(当这是你的全部意图时),没有微妙的bug。
作为题外话,你的最后一个问题包含了尚未被指定为合法的的声明(尽管被实现所接受,因为它几乎被同意它们应该是合法的)。这是CWG 2397期。但它的要点是,它的行为就像人们期望的函数参数中的数组类型一样。auto [2]
调整为auto*
,而auto(&) [2]
只绑定特定类型的数组。这是一个简化的函数模板,相当于:
template<typename T>
void f(T[2]);
template<typename T>
void f(T(&)[2]);
返回类型不像参数那样被调整。其中参数类型调整本身是C语言的遗产,从中我们得到的原始数组不是一等公民。就类型而言,它们是非常不规则的。如果你需要"更安全"的话;对于数组的值语义,您可以使用std::array
来实现此目的。
给定
decltype(auto) bar() { static int a[2]; return a; }
返回类型由decltype
演绎为a
的类型,即数组类型为int[2]
,不能指定为函数的返回类型
如果实参是未加括号的id表达式或未加括号的类成员访问表达式,则decltype产生由该表达式命名的实体的类型。
如果你想返回一个指针作为int*
,你可以
auto bar() { static int a[2]; return a; }
如果你想返回一个引用到数组作为int(&)[2]
,你可以
auto& bar() { static int a[2]; return a; }
或
decltype(auto) bar() { static int a[2]; return (a); }
// ^ ^
// for lvalue expression decltype yields T&; i.e. int(&)[2]
注意,如果对象的名称被括号括起来,它将被视为普通的左值表达式,因此
decltype(x)
和decltype((x))
通常是不同的类型。