返回指向常量数据成员和 'auto' 关键字的常量指针。有点糊涂

Returning a const pointer to a const data member and the 'auto' keyword. A bit confused

最近在学习C++,今天刚接触到const和const正确性的概念。为了更好地理解这个理论,我一直在编写一系列简单的程序来确保我正确理解了这个概念。我以为我明白了一切,但是当在其中一个程序中使用 auto 关键字时,我似乎有点卡住了。

为了测试我是否理解 const 指针的工作原理,我编写了一个简单的程序。我不会费心发布整件事,因为只有两部分是相关的。我有一个 class 和一个 int 类型的 const 数据成员:

const int tryToChangeMe;

在这个class中我还有一个成员函数return是一个指向上面的const int:

的const指针
const int* const MyClass::test()
{
    return &tryToChangeMe;
}

然后在我的主函数中调用上述函数,使用 auto 关键字。为了测试我认为我所知道的 const 是正确的,然后我尝试通过指针重新分配 tryToChangeMe 变量。像这样:

auto temp = myClass.test();
*temp = 100;

如我所料,由于我在尝试为 const 变量赋值时发生的错误,程序无法编译。但是,我不只是 return 一个指向 const 的指针,我 returned 一个 const 指向 const(至少我是这么认为的)。因此,为了对此进行测试,我尝试将指针重新分配给一个新的内存地址,我确信我会遇到类似的编译错误:

temp = new int;

但令人困惑的是程序编译没有任何问题。通过调试器逐步显示,果然,指针丢失了它的原始地址并被分配了一个全新的地址。想知道发生了什么,我只是碰巧删除了 auto 关键字并将其替换为变量的完整类型:

const int* const temp = myClass.test();

再次测试所有内容后,结果符合预期,这次我无法将指针重新分配到新地址。

毕竟我想我的问题是,为什么?为什么 auto 关键字允许您绕过指针的 const 限定符?我是不是做错了什么?

顺便说一下,我不确定这是否重要,但我使用的是 Visual Studio 2015 预览版

原因是auto个变量不是默认const。您 return const 值的事实并不意味着它必须分配给 const 变量;该值毕竟是 copied(尽管该值是一个指针)。您也可以使用显式类型规范轻松尝试。当更改变量 temp 时,存储在 myClass 中的值不会更改,指针目标仍然是 const,所以常量性仍然受到尊重。

写的时候

auto temp = rhs;

类型推导如下:

  • 如果rhs是引用,则忽略引用

  • top-level cv(const-volatile)-qualifiers of rhs 也被忽略(它们不会被忽略,但是如果你这样做auto& temp = rhs;; 在这种情况下,编译器模式匹配类型)

在你的例子中,右侧的类型是

const int* const
           ^^^^^
           top-level cv qualifier

const 指向 const-int 的指针。指针就像任何其他变量一样,所以它的 const-ness 将被丢弃(从技术上讲,顶级 cv 限定符是 const 并且它被丢弃),因此你最终得到 temp 被推断为

const int*

即指向 const-int 的非 const 指针,因此可以重新分配。如果你想强制执行 const-ness,那么你必须将左侧声明为

const auto temp = myClass.test();
^^^^^
need this

Scott Meyers 有一篇 excellent introduction to the subject (also available on his Effective Modern C++ book, Items 1 and 2 free to browse here),他在其中解释了 template 类型推导的工作原理。一旦你理解了这一点,理解 auto 就轻而易举了,因为 auto 类型推导实际上非常接近模板类型推导系统(std::initializer_list<> 是一个明显的例外)。

编辑

还有一条附加规则
auto&& temp = rhs;

但要理解它,您需要理解 how forwarding (universal) references work and how reference collapsing works

如前所述,auto 忽略顶级 cv 限定符。阅读 this article 了解 autodecltype 工作原理的详细信息。

现在,即使 auto 没有忽略 const,在你的情况下,temp 仍然不会是 const,因为顶级 cv-qualifiers return 类型是 ignored 如果被 returned 的类型是非 class 类型。

g++ 甚至使用 -Wextra

产生以下警告

warning: type qualifiers ignored on function return type [-Wignored-qualifiers]

这可以通过使用 C++14 的 decltype(auto) 来证明。与 auto 不同,decltype(auto) 不会丢弃引用和顶级 cv 限定符。如果您通过添加以下行修改您的示例,代码仍将编译,证明 temp 不是 const 指针。

decltype(auto) temp = myClass.test();
static_assert(std::is_same<const int*, decltype(temp)>{}, "");

另一方面,如果 test() return 是具有顶级 cv 限定符的 class 类型的对象,那么 auto 仍会丢弃 const,但 decltype(auto) 不会。

Live demo

我将为标准参考搜索者提供来自标准的一些正式解释。:

N4296::7.1.6.4/7 [dcl.spec.auto]

If the placeholder is the auto type-specifier, the deduced type is determined using the rules for template argument deduction.

现在,模板参数推导 N4296::14.8.2/3 [temp.deduct]:

[...] the function parameter type adjustments described in 8.3.5 are performed.

最后 N4296::8.3.5/5 [dcl.fct]

After determining the type of each parameter, any parameter of type “array of T” or “function returning T” is adjusted to be “pointer to T” or “pointer to function returning T,” respectively. After producing the list of parameter types, any top-level cv-qualifiers modifying a parameter type are deleted when forming the function type.

简而言之,是的,在这种情况下,CV 限定符将被忽略。