我一直认为C++17中的
auto
是实际数据类型的占位符,但在下面的例子中却并非如此:
tuple<int, int> permute(int a, int b){return {b,a};}
int main(){
auto [x,y] = permute(2,5);
cout<<x<<","<<y<<endl;
tuple<int, int> {x,y} = permute(2,5);
cout<<x<<","<<y<<endl;
return 0;
}
为什么
auto
需要 [ , ]
而元组需要大括号?
编辑:如下所示,
[ , ]
是C++17结构化绑定声明。尽管如此,我的问题仍然是:为什么我不能用 tuple<int, int> {x,y}
替换 auto {x,y}
?
auto [/*...*/]
是与其他声明完全不同的语法构造(并且仅自 C++17 起才受支持,顺便说一句)。
它是所谓的结构化绑定,而不是简单的变量声明。
auto
关键字和括号是该特殊语法的一部分。它们不是(直接)正在声明的变量的类型,也不是此类变量的初始值设定项。
auto
用于结构化绑定声明的行为,以提供从右侧初始化的(未命名)变量的声明,就像通过:
auto _unnamed = permute(2,5);
在您的示例中,
auto
将被推导为 std::tuple<int,int>
。 x
和 y
的行为类似于对此 _unnamed
变量的第一个和第二个元素的引用。
结构化绑定本质上与
std::tuple
没有任何联系。任何数组类型、聚合类型或提供特定 std::tuple_element
/std::get
接口的类型都可以与结构化绑定一起使用。
至于为什么不允许以这种形式的声明直接指定显式类型而不是
auto
,3.6节中的proposal给出的唯一原因是人们总是可以显式地指定右侧的类型如果需要的话侧面:
auto [x,y] = tuple<int,int>(permute(2,5));
我认为没有任何其他原因不允许这样做。这看起来有点像提案作者的个人喜好。
在
tuple<int, int> {x,y}
中,大括号是对象的初始值设定项,即在该表达式中创建的 tuple<int, int>
类型的临时对象。
请注意,这根本不是声明。它使用先前结构化绑定声明中的
x
和 y
来初始化临时对象。它没有声明 x
或 y
。然后,您将 permute()
的结果分配给这个临时对象,这实际上不会执行任何操作,因为该对象在表达式末尾立即被销毁。
一个简单的声明如下所示:
tuple<int, int> t = permute(2,5);
这不允许直接通过元素自己的名称访问元素。如果您不使用结构化绑定语法,则必须专门为它们引入名称作为引用:
int& x = std::get<0>(t);
int& y = std::get<1>(t);
无论如何,为什么我不能将
替换为tuple<int, int> {x,y}
?auto {x,y}
std::tuple
都不是 C++ 中的特殊类型。仅仅因为您使用大括号并不意味着您想要形成一个元组。您可以为任何合适的聚合类型 S{x,y}
编写 S
。如果该语言比其他可能的类型更喜欢tuple
,那就很奇怪了。
在许多其他语言中,元组类型不仅是像 C++ 中的库类型,而且与核心语言有更根本的联系。在这种情况下,语言通常会提供特殊的语法来形成元组,例如如
(x, y)
。在 C++ 中,元组不是内置类型,在核心语言中没有特殊含义,并且没有专用于它们的语法。
但是,您可以写
tuple{x,y}
。从 C++17 开始,可以通过 CTAD(类模板参数推导)推导元素类型。
话虽如此,标准库中确实有一种特殊类型,每当使用大括号时,它都是首选的(人们可能会争论这样设计是否是一个好的决定)。然而,这种首选类型不是
std::tuple
,而是 std::initializer_list
,这是一种神奇类型,其行为类似于对隐式创建的数组的可复制引用,即它仅适用于同类元素类型,并且不适用复制时不提供值语义。
因此,例如在这段代码中:
auto a = {x, y};
a
的类型将被推导为std::initializer_list<int>
。这主要用于 range-for
循环中的迭代,以及传递给容器的构造函数。因此,即使偏爱一种类型而不是另一种类型并不奇怪,C++ 已经选择了除 std::tuple
之外的另一种类型。