这是 C++ 中的一个怪癖/功能。尽管我们不认为引用是类型,但它们实际上“位于”类型系统中。虽然这看起来很尴尬(考虑到当使用引用时,引用语义会自动发生并且引用“不碍事”),但有一些合理的理由可以解释为什么引用在类型系统中建模而不是作为外部的单独属性输入。
首先,让我们考虑并非声明名称的每个属性都必须在类型系统中。从C语言开始,我们有“存储类”和“联动”。可以引入一个名称为extern const int ri,其中extern 表示静态存储类和链接的存在。类型只是const int。
C++ 显然接受了表达式具有类型系统之外的属性的概念。该语言现在有一个“值类”的概念,它试图组织表达式可以展示的越来越多的非类型属性。
然而引用是类型。为什么?
曾经在 C++ 教程中解释过,像 const int &ri 这样的声明将 ri 引入为具有类型 const int,但引用语义。那个引用语义不是一种类型。它只是一种属性,表示名称和存储位置之间的异常关系。此外,引用不是类型这一事实被用来解释为什么不能基于引用构造类型,即使类型构造语法允许这样做。例如,数组或指向引用的指针是不可能的:const int &ari[5] 和 const int &*pri。
但实际上引用 是 类型,因此decltype(ri) 检索到一些不合格的引用类型节点。您必须下降到类型树中的此节点才能到达具有remove_reference 的基础类型。
当您使用ri 时,引用被透明地解析,因此ri“看起来和感觉就像i”并且可以称为它的“别名”。不过,在类型系统中,ri 实际上确实有一个类型是“reference to const int”。
为什么是引用类型?
考虑如果引用是不是类型,那么这些函数将被认为具有相同的类型:
void foo(int);
void foo(int &);
这根本不可能是出于不言而喻的原因。如果它们具有相同的类型,则意味着任一声明都适用于任一定义,因此每个 (int) 函数都必须被怀疑引用。
同样,如果引用不是类型,那么这两个类声明将是等价的:
class foo {
int m;
};
class foo {
int &m;
};
一个翻译单元使用一个声明,而同一程序中的另一个翻译单元使用另一个声明是正确的。
事实上,引用意味着实现上的差异,并且不可能将其与类型分开,因为 C++ 中的类型与实体的实现有关:它的“布局”在位可以这么说。如果两个函数具有相同的类型,则可以使用相同的二进制调用约定来调用它们:ABI 是相同的。如果两个结构或类具有相同的类型,则它们的布局以及访问所有成员的语义相同。引用的存在改变了类型的这些方面,因此将它们合并到类型系统中是一个简单的设计决策。 (但是,请注意这里的反驳:结构/类成员可以是static,这也会改变表示;但这不是类型!)
因此,引用在类型系统中是“二等公民”(与 ISO C 中的函数和数组不同)。有些事情我们不能用引用“做”,例如声明指向引用的指针或它们的数组。但这并不意味着它们不是类型。它们只是不是有意义的类型。
并非所有这些二等限制都是必不可少的。鉴于存在引用结构,可能存在引用数组!例如
// fantasy syntax
int x = 0, y = 0;
int &ar[2] = { x, y };
// ar[0] is now an alias for x: could be useful!
这只是没有在 C++ 中实现,仅此而已。但是,指向引用的指针根本没有意义,因为从引用中提取的指针只是指向被引用的对象。没有引用数组的可能原因是,C++ 人认为数组是从 C 继承的一种低级特性,在许多方面都被破坏,无法修复,他们不想将数组作为任何新事物的基础。但是,引用数组的存在将清楚地说明引用必须是类型。
非const-qualifiable 类型:也在 ISO C90 中找到!
一些答案暗示引用不采用const 限定符这一事实。那是一条红鲱鱼,因为声明 const int &ri = i 甚至都没有尝试进行 const 限定的引用:它是对 const 限定类型的引用(它本身不是 @ 987654345@)。就像 const in *ri 声明了一个指向 const 的指针,但该指针本身不是 const。
也就是说,引用本身不能携带const 限定符。
然而,这并不奇怪。即使在 ISO C 90 语言中,也不是所有类型都可以是 const。即不能是数组。
首先,声明 const 数组的语法不存在:int a const [42] 是错误的。
然而,上述声明试图做的事情可以通过一个中间typedef来表达:
typedef int array_t[42];
const array_t a;
但这并没有像它看起来那样做。在这个声明中,获得const 资格的不是a,而是元素!也就是说,a[0] 是一个const int,但a 只是“int 数组”。因此,这不需要诊断:
int *p = a; /* surprise! */
这样做:
a[0] = 1;
再次强调,引用在某种意义上是类型系统中的“第二类”,就像数组一样。
请注意这个类比是如何更深入的,因为数组也有一个“不可见的转换行为”,比如引用。程序员不必使用任何显式运算符,标识符a 自动变成int * 指针,就好像使用了表达式&a[0]。这类似于引用ri,当我们将其用作主要表达式时,它神奇地表示它所绑定的对象i。这只是另一个“衰减”,就像“数组到指针衰减”。
就像我们不能被“指向指针的数组”迷惑而错误地认为“数组只是 C 和 C++ 中的指针”一样,我们同样不能认为引用只是没有类型的别名他们自己的。
当decltype(ri) 抑制通常的引用到其所指对象的转换时,这与sizeof a 抑制数组到指针的转换并在数组类型本身上进行操作并没有太大区别 计算它的大小。