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

Tro*_*yvs 85 c++ const reference decltype language-lawyer

我们知道"const变量"表示一旦分配,就无法更改变量,如下所示:

int const i = 1;
i = 2;
Run Code Online (Sandbox Code Playgroud)

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

assignment of read-only variable 'i'
Run Code Online (Sandbox Code Playgroud)

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

#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;
}
Run Code Online (Sandbox Code Playgroud)

它输出

true
false
Run Code Online (Sandbox Code Playgroud)

奇怪的.我们知道,一旦引用绑定到名称/变量,我们就无法更改此绑定,我们更改其绑定对象.所以我认为类型ri应该是相同的i:什么时候iint const,为什么ri不是const

son*_*yao 54

为什么"ri"不是"const"?

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

如果T是const限定类型(即const或const volatile),则提供成员常量值等于true.对于任何其他类型,值为false.

但是引用不能是const限定的.$ 8.3.2/1参考文献[dcl.ref]

除非通过使用typedef-name([dcl.typedef],[temp.param])或decltype-specifier([dcl.type.simple])引入cv-qualifiers,否则Cv限定的引用是不正确的. ,在这种情况下,cv限定符被忽略.

因此is_const<decltype(ri)>::value将返回falsebecuase ri(引用)不是const限定类型.正如你所说,我们不能在初始化后重新引用引用,这意味着引用始终是"const",另一方面,const限定引用或const不合格引用实际上可能没有意义.

  • @ user9999999其他答案也没有真正回答这个问题.如果引用不能反弹,为什么`is_const`不返回`true`?这个答案试图对指针如何可选择地重新组合进行类比,而引用则不是 - 并且这样做会导致出于自相矛盾的原因.我不确定是否有任何真正的解释,除了那些编写标准的人有点武断的决定,有时这是我们所希望的最好的.因此这个答案. (5认同)
  • 直接达到标准,因此直截了当,而不是所接受的答案的所有莫名其妙的假设. (4认同)
  • @underscore_d这是一个很好的答案,直到你认识到op也在问为什么."因为它说的如此"对于问题的这个方面并不真正有用. (4认同)
  • 为了进一步阐明@KyleStrand的评论:`decltype(name)`与一般的`decltype(expr)`的行为不同.因此,例如,`decltype(i)`是`i`的声明类型,它是`const int`,而`decltype((i))`将是`int const&`. (3认同)
  • 我认为另一个重要方面是`decltype`是*不是函数*因此*直接在引用本身*上工作,而不是在引用对象上工作.(这可能与"引用基本上是指针"的答案更相关,但我仍然认为这是使这个例子混淆的一部分,因此值得一提.) (2认同)

Gal*_*lik 52

这看似违反直觉,但我认为理解这一点的方法是要认识到,在某些方面,引用句法上被理解为像指针一样.

这似乎是指针的逻辑:

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;
}
Run Code Online (Sandbox Code Playgroud)

输出:

true
false
Run Code Online (Sandbox Code Playgroud)

这是合乎逻辑的,因为我们知道它不是指针对象是const(它可以指向别处)它是被指向的对象.

所以,我们看到正确的常量性的的指针本身返回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;
}
Run Code Online (Sandbox Code Playgroud)

输出:

true
true
Run Code Online (Sandbox Code Playgroud)

所以我认为我们看到了与参考文献的句法类比.

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

因此,即使引用指针共享相同的语法,规则也是不同的,因此语言阻止我们像这样声明引用本身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;
}
Run Code Online (Sandbox Code Playgroud)

我假设我们不允许这样做,因为当语言规则阻止引用以与指针相同的方式(如果未声明const)被反弹时,似乎不需要它.

所以回答这个问题:

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

在您的示例中,语法使得引用事物const的方式与声明指针时的方式相同.

不管正确与否,我们不能使基准本身const,但如果我们将这个样子:

int const& const ri = i; // not allowed
Run Code Online (Sandbox Code Playgroud)

Q)我们知道一旦引用绑定到名称/变量,我们就无法更改此绑定,我们更改其绑定对象.所以我认为类型ri应该相同i:何时i是a int const,为什么ri不是const

为什么decltype()没有转移到引用绑定的对象?

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

  • 听起来你说"引用不能是const因为它们总是const"? (13认同)
  • 这里有许多蜿蜒的假设和可疑的类比指针,我认为这个问题比必要的更加困惑,当像sonyuanyao这样的标准检查确实直接达到真正的要点时:引用不是cv-qualifiable类型,因此它不能是`const`,因此`std :: is_const`必须返回`false`.他们可能会使用措辞,这意味着它必须返回'true`,但他们没有.而已!所有这些关于指针的东西,"我假设","我想"等等都没有提供任何真正的阐述. (9认同)
  • 可能确实"*语义上*绑定后对它们的所有操作都转移到它们所指的对象上",但是OP也期望它也适用于`decltype`,并且发现它没有. (2认同)

che*_*989 18

您需要使用它std::remove_reference来获取您正在寻找的价值.

std::cout << std::is_const<std::remove_reference<decltype(ri)>::type>::value << std::endl;
Run Code Online (Sandbox Code Playgroud)

有关更多信息,请参阅此帖子.


Lig*_*ica 7

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

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

由于引用类型只是 - 类型 - 它可能在某些意义上要求const它们上的-qualifier与其他类型(特别是指针类型)对称,但这会很快变得非常繁琐.

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

事实上,它们是不可改变的.

并且,由于它们const不合格,因此在引用类型上产生真值可能会更加困惑is_const.

我发现这是一个合理的妥协,特别是因为不变性无论如何都是由于没有语法来改变引用而强制执行的.


Kaz*_*Kaz 5

这是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实际上确实具有" 引用 const int " 的类型.

为什么引用类型?

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

void foo(int);
void foo(int &);
Run Code Online (Sandbox Code Playgroud)

这根本不是出于几乎不言而喻的原因.如果它们具有相同的类型,则意味着任何一个声明都适用于任一定义,因此(int)必须怀疑每个函数都需要引用.

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

class foo {
  int m;
};

class foo {
  int &m;
};
Run Code Online (Sandbox Code Playgroud)

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

事实是,引用意味着实现上的差异,并且不可能将它与类型分开,因为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!
Run Code Online (Sandbox Code Playgroud)

这只是在C++中实现的,就是这样.但是,引用的指针根本没有意义,因为从引用中提取的指针只会转到引用的对象.可能的原因,为什么没有引用数组是C++的人认为阵列是一种低层次的功能,从C作为在很多方面是无法挽回的破碎继承的,他们不想碰阵列作为什么新的基础.但是,引用数组的存在将清楚地说明引用必须是何种类型.

const-qualifiable类型:在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;
Run Code Online (Sandbox Code Playgroud)

但这并不像它看起来那样做.在此声明,这不是a它获取const资格,但是元素!也就是说,a[0]是一个const int,但a只是"数组的int".因此,这不需要诊断:

int *p = a; /* surprise! */
Run Code Online (Sandbox Code Playgroud)

这样做:

a[0] = 1;
Run Code Online (Sandbox Code Playgroud)

同样,这强调了这样一种观点,即类型系统中的引用在某种意义上是"第二类",就像数组一样.

请注意类比如何更深入,因为数组也具有"不可见的转换行为",如引用.如果程序员不必使用任何显式运算符,标识符将a自动变为int *指针,就像表达式&a[0]已被使用一样.这类似于ri当我们将它用作主表达式时,引用如何神奇地表示i它所绑定的对象.它只是另一个"衰变",就像"指针衰减的数组".

就像我们不能被"数组到指针"一样混淆而错误地认为"数组只是C和C++中的指针",我们同样不能认为引用只是没有自己类型的别名.

decltype(ri)抑制通常将引用转换为它的引用对象时,这与sizeof a抑制数组到指针转换并且对数组类型本身进行操作以计算其大小没有太大区别.