为什么在 C++ 中引用不是 "const"?

Why are references not "const" in C++?

我们知道,一个"const variable"表示变量一旦赋值,就不能再改变了,像这样:

int const i = 1;
i = 2;

上面的程序将无法编译; gcc提示错误:

assignment of read-only variable 'i'

没问题,我能理解,但是下面的例子超出了我的理解范围:

#include<iostream>
using namespace std;
int main()
{
    boolalpha(cout);
    int const i = 1;
    cout << is_const<decltype(i)>::value << endl;
    int const &ri = i;
    cout << is_const<decltype(ri)>::value << endl;
    return 0;
}

输出

true
false

奇怪。我们知道一旦一个引用绑定到一个name/variable,我们就不能改变这个绑定,我们改变它的绑定对象。所以我想 ri 的类型应该与 i 相同:当 iint const 时,为什么 ri 不是 const

您需要使用 std::remove_reference 来获取您要查找的值。

std::cout << std::is_const<std::remove_reference<decltype(ri)>::type>::value << std::endl;

有关详细信息,请参阅

这似乎违反直觉,但我认为理解这一点的方法是认识到,在某些方面,引用在语法上[=88] =]喜欢指针.

对于 指针这似乎合乎逻辑:

int main()
{
    boolalpha(cout);

    int const i = 1;
    cout << is_const<decltype(i)>::value << endl;

    int const* ri = &i;
    cout << is_const<decltype(ri)>::value << endl;
}

输出:

true
false

这是合乎逻辑的,因为我们知道它不是 指针对象 是常量(它可以指向其他地方)它是被指向的对象。

所以我们正确地看到 指针 本身的 constness 返回为 false.

如果我们想要指针本身const我们必须说:

int main()
{
    boolalpha(cout);

    int const i = 1;
    cout << is_const<decltype(i)>::value << endl;

    int const* const ri = &i;
    cout << is_const<decltype(ri)>::value << endl;
}

输出:

true
true

所以我认为我们看到了与 reference.

的句法类比

但是引用在语义上不同于指针,尤其是在一个关键方面,我们不允许重新绑定 绑定后对另一个对象的引用。

因此,尽管 referencespointers 共享相同的语法,但规则不同,因此语言阻止我们声明 引用本身const像这样:

int main()
{
    boolalpha(cout);

    int const i = 1;
    cout << is_const<decltype(i)>::value << endl;

    int const& const ri = i; // COMPILE TIME ERROR!
    cout << is_const<decltype(ri)>::value << endl;
}

我假设我们不允许这样做,因为当语言规则阻止 reference 以与 指针 可以(如果未声明const)。

所以回答问题:

Q) Why “reference” is not a “const” in C++?

在您的示例中,语法使被引用的事物 const 与您声明 pointer.

的方式相同

无论对错,我们都不允许引用本身const,但如果我们这样做的话,它看起来像这样:

int const& const ri = i; // not allowed

Q) we know once a reference is bind to a name/variable, we cannot change this binding, we change its binded object. So I suppose the type of ri should be same as i: when i is a int const, why ri is not const?

为什么 decltype() 没有传递给 referece 绑定的对象?

我想这是为了与指针语义等价,也许decltype()(声明的类型)的功能是回顾在绑定发生之前声明

why is "ri" not "const"?

std::is_const 检查类型是否为 const 限定。

If T is a const-qualified type (that is, const, or const volatile), provides the member constant value equal true. For any other type, value is false.

但是引用不能是 const 限定的。 References [dcl.ref]/1

Cv-qualified references are ill-formed except when the cv-qualifiers are introduced through the use of a typedef-name ([dcl.typedef], [temp.param]) or decltype-specifier ([dcl.type.simple]), in which case the cv-qualifiers are ignored.

所以 is_const<decltype(ri)>::value 将 return false 因为 ri (引用)不是 const 限定类型。正如您所说,我们不能在初始化后重新绑定引用,这意味着引用始终是 "const",另一方面,const 限定引用或 const 非限定引用实际上可能没有意义。

const X& x”表示 x 为 X 对象起别名,但您不能通过 x 更改该 X 对象。

并参见 std::is_const

这是 C++ 中的 quirk/feature。虽然我们不认为引用是类型,但实际上它们在类型系统中 "sit" 。尽管这看起来很尴尬(假设在使用引用时,引用语义会自动出现并且引用 "gets out of the way"),但有一些合理的理由可以解释为什么引用在类型系统中建模而不是作为类型之外的单独属性。

首先,让我们考虑一下,并非声明名称的每个属性都必须在类型系统中。在C语言中,我们有"storage class"和"linkage"。名称可以引入为 extern const int ri,其中 extern 表示静态存储 class 和链接的存在。类型只是 const int.

C++ 显然接受了表达式具有类型系统之外的属性的概念。该语言现在有一个概念 "value class",它试图组织表达式可以展示的越来越多的非类型属性。

然而引用是类型。为什么?

过去在 C++ 教程中解释说,像 const int &ri 这样的声明将 ri 引入为具有类型 const int,但引用语义。该引用语义不是一种类型;它只是一种表示名称和存储位置之间不寻常关系的属性。此外,引用不是类型这一事实被用来解释为什么你不能基于引用构造类型,即使类型构造语法允许这样做。例如,数组或指向引用的指针是不可能的:const int &ari[5]const int &*pri.

但实际上引用 类型,因此 decltype(ri) 检索一些不合格的引用类型节点。您必须下降到类型树中的此节点之后才能到达具有 remove_reference.

的基础类型

当你使用ri时,引用被透明地解析,所以ri "looks and feels like i" 可以为它调用一个"alias"。但是,在类型系统中,ri 实际上有一个类型是“reference to const int”。

为什么引用是类型?

考虑如果引用是 不是 类型,那么这些函数将被认为具有相同的类型:

void foo(int);
void foo(int &);

这根本不可能是不言自明的原因。如果它们具有相同的类型,则意味着任一声明都适用于任一定义,因此每个 (int) 函数都必须被怀疑采用引用。

类似地,如果引用不是类型,那么这两个 class 声明将是等价的:

class foo {
  int m;
};

class foo {
  int &m;
};

一个翻译单元使用一个声明,而同一程序中的另一个翻译单元使用另一个声明是正确的。

事实是,引用意味着实现上的差异,并且不可能将其与类型分开,因为 C++ 中的类型与实体的实现有关:它的 "layout" 可以这么说。如果两个函数具有相同的类型,则可以使用相同的二进制调用约定调用它们:ABI 相同。如果两个结构或 classes 具有相同的类型,则它们的布局以及访问所有成员的语义相同。引用的存在改变了类型的这些方面,因此将它们合并到类型系统中是一个简单的设计决策。 (但是,请注意这里的反驳:struct/class 成员可以是 static,这也会改变表示;但这不是类型!)

因此,引用在类型系统中为 "second class citizens"(与 ISO C 中的函数和数组不同)。有些事情我们不能对引用 "do" ,例如声明指向引用的指针或它们的数组。但这并不意味着它们不是类型。它们只是不是有意义的类型。

并非所有这些第二-class-限制都是必不可少的。鉴于存在引用结构,可能存在引用数组!例如

// 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限定类型:也在 ISO C90 中找到!

一些答案暗示了引用不带 const 限定符的事实。这是一个转移注意力的问题,因为声明 const int &ri = i 甚至没有 尝试 来进行 const 限定引用:它是对 const 限定的引用类型(它本身不是 const)。就像 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只是"array of int"。因此,这不需要诊断:

int *p = a; /* surprise! */

这样做:

a[0] = 1;

同样,这强调了引用在某种意义上 "second class" 在类型系统中的想法,就像数组一样。

请注意这个类比如何更深入,因为数组也有一个 "invisible conversion behavior",就像引用一样。程序员不必使用任何显式运算符,标识符 a 会自动变成一个 int * 指针,就好像使用了表达式 &a[0] 一样。这类似于引用 ri,当我们将其用作主要表达式时,如何神奇地表示它所绑定的对象 i。这只是另一个 "decay" 就像 "array to pointer decay".

并且就像我们不能被 "array to pointer" 混淆而误认为 "arrays are just pointers in C and C++" 一样,我们同样不能认为引用只是没有自己类型的别名。

decltype(ri) 抑制对其引用对象的引用的通常转换时,这与 sizeof a 抑制数组到指针的转换并在 [=113 上操作没有太大区别=]数组类型本身来计算它的大小。

为什么宏不是 const?职能?文字?类型名称?

const事物只是不可变事物的一个子集。

因为引用类型就是——类型——为了与其他类型(尤其是指针类型)对称,要求对它们全部使用 const-限定符可能是有意义的,但这会变得非常很快就乏味了。

如果 C++ 默认有不可变对象,要求在任何你不想 const 的东西上使用 mutable 关键字,那么这将有很简单:根本不允许程序员将 mutable 添加到引用类型。

事实上,它们是不可变的。

而且,由于它们不是 const 限定的,因此可能 更多 混淆 is_const 在引用类型上产生 true。

我发现这是一个合理的妥协,尤其是因为不可变性是通过不存在任何语法来改变引用这一事实强制执行的。