使用rvalue引用const char*的重载决策

ali*_*ice 6 c++ rvalue lvalue c++11

#include <iostream>

using namespace std;

void f(const char * const &s) {
    cout << "lvalue" << endl;
}

void f(const char * const &&s) {
    cout << "rvalue" << endl;
}

int main()
{
    char s[] = "abc";

    f("abc");
    f(s);
}
Run Code Online (Sandbox Code Playgroud)

输出:

rvalue
rvalue
Run Code Online (Sandbox Code Playgroud)

为什么输出"rvalue lvalue"不是?

Win*_*ute 4

字符串文字也不是s指针(它们是数组),因此标准的相关部分是 [conv.array]:

“数组N T”或“未知边界数组T”类型的左值或右值可以转换为“指向T”的指针类型的纯右值。结果是指向数组第一个元素的指针。

注意

char const *p = s;
f(p);
Run Code Online (Sandbox Code Playgroud)

打印“左值”,以表明这按照您对指针的预期工作。

附录:评论:如果

char *p = s;
f(p);
Run Code Online (Sandbox Code Playgroud)

如果右值重载存在,则打印“rvalue”,但如果删除它,则不会导致编译器错误,标准的其他两个部分开始发挥作用——其中一个似乎完全禁止绑定到char*char const *const &另一个打开一个窗口回来。

第一个是 [dcl.init.ref]/4,其中指出

给定类型“ cv1 T1 ”和“ cv2 T2 ”,如果“ cv1 T1 ”与“ cv2 ”类型相同,或者是 的基类,则“cv1 ”与“cv2 ”引用相关。如果“ cv1 ”与“ cv2 ”引用相关,并且cv1与cv2具有相同的 cv 限定,或者比 cv2 更高,则“ cv1 ”与“ cv2 ”引用兼容。(...) T2T1T2T1T2 T1 T2T1T2

它详细介绍了参考初始化的精确规则,所有这些都是相关的,但不幸的是对于一个如此答案来说太长了。长话短说,如果cv1 的引用是引用兼容的,则可以使用cv2T1的对象来初始化对cv1 的引用。 T2

这种法律术语对于我们的情况意味着char*char const *不是引用兼容的(尽管char*并且char *const将会是),因为一个char*不是char const *也不是另一个的基类。如果您认为以下非法代码段在其他情况下是合法的,则此限制是有意义的:

const char c = 'c';
char *pc;
const char*& pcc = pc;   // #1: not allowed
pcc = &c;
*pc = 'C';               // #2: modifies a const object
Run Code Online (Sandbox Code Playgroud)

这是改编自 [conv.qual]/4 中的类似示例,该示例使用指向指针的指针来演示相同的问题。

[conv.qual] 也是打开窗口的其他相关部分。它在 [conv.qual]/1 中表示:

如果“ cv2 ”比“ cv1 ”更具 cv限定,则“指向cv1 的T指针”类型的纯右值可以转换为“指向 cv2 的指针类型的纯右值 T T T

由此可知char*可以转换为char const *1(与 引用兼容),这就是为什么如果删除char const *const的右值重载,代码仍然可以编译。f但是,此转换的结果是纯右值,因此如果存在,则在重载决策中首选右值重载。

1 char*泛左值 ->char*纯右值 (by [conv.lval]) ->char const *纯右值)