指针、引用与 const 的类型推导

在 《使用 auto 关键字进行对象的类型推导》中,我们讨论了如何使用 auto 关键字让编译器从初始化器推导变量的类型:

int main()
{
    int a{ 5 };
    auto b{ a }; // b 被推导为 int 类型

    return 0;
}

我们还指出,默认情况下,类型推导会丢弃类型中的 const

int main()
{
    const double a{ 7.8 }; // a 的类型是 const double
    auto b{ a };           // b 的类型是 double(const 被丢弃)

    constexpr double c{ 7.8 }; // c 的类型是 const double(constexpr 隐式应用 const)
    auto d{ c };               // d 的类型是 double(const 被丢弃)

    return 0;
}

可以通过在推导出的类型定义中添加 const(或 constexpr)限定符来重新应用 const(或 constexpr):

int main()
{
    double a{ 7.8 };    // a 的类型是 double
    const auto b{ a };  // b 的类型是 const double(重新应用 const)

    constexpr double c{ 7.8 }; // c 的类型是 const double(constexpr 隐式应用 const)
    const auto d{ c };         // d 是 const double(const 被丢弃,重新应用 const)
    constexpr auto e{ c };     // e 是 constexpr double(const 被丢弃,重新应用 constexpr)

    return 0;
}

类型推导会丢弃引用

除了会丢弃 const,类型推导还会丢弃引用:

#include <string>

std::string& getRef(); // 某个返回引用的函数

int main()
{
    auto ref{ getRef() }; // 类型被推导为 std::string(而非 std::string&)

    return 0;
}

在上面的例子中,变量 ref 使用了类型推导。尽管函数 getRef() 返回的是 std::string&,但引用限定符被丢弃了,因此 ref 的类型被推导为 std::string

就像丢弃 const 一样,如果你希望推导出的类型是引用,可以在定义点重新应用引用:

#include <string>

std::string& getRef(); // 某个返回引用的函数

int main()
{
    auto ref1{ getRef() };  // std::string(引用被丢弃)
    auto& ref2{ getRef() }; // std::string&(引用被丢弃,重新应用引用)

    return 0;
}

顶级 const 与低级 const

顶级 const 是应用于对象本身的 const 限定符。例如:

const int x;    // 这个 const 应用于 x,因此它是顶级的
int* const ptr; // 这个 const 应用于 ptr,因此它是顶级的
// 引用没有顶级 const 的语法,因为它们隐式地是顶级 const

相对地,低级 const 是应用于被引用或被指向对象的 const 限定符:

const int& ref; // 这个 const 应用于被引用的对象,因此它是低级的
const int* ptr; // 这个 const 应用于被指向的对象,因此它是低级的

对常量值的引用总是低级 const。指针可以有顶级、低级或两种 const:

const int* const ptr; // 左边的 const 是低级的,右边的 const 是顶级的

当我们说类型推导会丢弃 const 限定符时,它只会丢弃顶级 const。低级 const 不会被丢弃。我们马上会看到相关示例。

类型推导与 const 引用

如果初始化器是对 const 的引用,首先会丢弃引用(然后在适用的情况下重新应用),然后从结果中丢弃任何顶级 const:

#include <string>

const std::string& getConstRef(); // 某个返回对 const 的引用的函数

int main()
{
    auto ref1{ getConstRef() }; // std::string(引用被丢弃,然后从结果中丢弃顶级 const)

    return 0;
}

在上面的例子中,由于 getConstRef() 返回的是 const std::string&,首先会丢弃引用,剩下 const std::string。这个 const 现在是顶级 const,因此它也会被丢弃,最终推导出的类型是 std::string

关键要点

丢弃引用可能会将低级 const 转换为顶级 const:const std::string& 是低级 const,但丢弃引用后得到 const std::string,这是顶级 const。

我们可以重新应用引用和/或 const:

#include <string>

const std::string& getConstRef(); // 某个返回对 const 的引用的函数

int main()
{
    auto ref1{ getConstRef() };        // std::string(引用和顶级 const 被丢弃)
    const auto ref2{ getConstRef() };  // const std::string(引用被丢弃,const 被丢弃,重新应用 const)

    auto& ref3{ getConstRef() };       // const std::string&(引用被丢弃并重新应用,低级 const 未被丢弃)
    const auto& ref4{ getConstRef() }; // const std::string&(引用被丢弃并重新应用,低级 const 未被丢弃)

    return 0;
}

ref1 的情况在前面的例子中已经讨论过。对于 ref2,这与 ref1 的情况类似,只是我们重新应用了 const 限定符,因此推导出的类型是 const std::string

ref3 的情况变得更有意思。通常引用会首先被丢弃,但由于我们重新应用了引用,因此引用不会被丢弃。这意味着类型仍然是 const std::string&。而且由于这个 const 是低级 const,因此它不会被丢弃。因此,推导出的类型是 const std::string&

ref4 的情况与 ref3 类似,只是我们还重新应用了 const 限定符。由于类型已经被推导为对 const 的引用,因此在这里重新应用 const 是多余的。尽管如此,使用 const 可以明确表明我们的结果将是 const(而在 ref3 的情况下,结果的 const 性是隐式的,不明显)。

最佳实践

即使在严格意义上并非必要,如果你想得到一个 const 引用,也应该重新应用 const 限定符,因为它可以明确你的意图并帮助防止错误。

关于 constexpr 引用呢?

constexpr 不是表达式类型的一部分,因此不会被 auto 推导。

提醒

定义 const 引用(如 const int&)时,const 应用于被引用的对象,而非引用本身。

定义对 const 变量的 constexpr 引用(如 constexpr const int&)时,需要同时应用 constexpr(应用于引用)和 const(应用于被引用的类型)。相关内容见 12.4 课《对 const 的左值引用》。

#include <string_view>
#include <iostream>

constexpr std::string_view hello{ "Hello" };   // 隐式为 const

constexpr const std::string_view& getConstRef() // 函数是 constexpr,返回对 const std::string_view 的引用
{
    return hello;
}

int main()
{
    auto ref1{ getConstRef() };                  // std::string_view(引用和顶级 const 被丢弃)
    constexpr auto ref2{ getConstRef() };        // constexpr const std::string_view(引用和顶级 const 被丢弃,应用 constexpr,隐式为 const)

    auto& ref3{ getConstRef() };                 // const std::string_view&(引用被重新应用,低级 const 未被丢弃)
    constexpr const auto& ref4{ getConstRef() }; // constexpr const std::string_view&(引用被重新应用,低级 const 未被丢弃,应用 constexpr)

    return 0;
}

类型推导与指针

与引用不同,类型推导不会丢弃指针:

#include <string>

std::string* getPtr(); // 某个返回指针的函数

int main()
{
    auto ptr1{ getPtr() }; // std::string*

    return 0;
}

我们还可以使用星号(auto*)与指针类型推导结合,以更清楚地表明推导出的类型是指针:

#include <string>

std::string* get

关注公众号,回复"cpp-tutorial"

可领取价值199元的C++学习资料

公众号二维码

扫描上方二维码或搜索"cpp-tutorial"