jac*_*k X 6 c++ language-lawyer
#include <iostream>
struct A{
A() = default;
A(volatile const A&){}
void show()const volatile {
}
};
int main(){
volatile A a;
//A b = std::move(a); // ill-formed
std::move(a).show(); //OK
}
Run Code Online (Sandbox Code Playgroud)
对于A b = std::move(a);,它是格式错误的,因为它违反了以下规则,即:
dcl.init.ref#5.2
否则,如果引用是对非 const 限定或volatile 限定的类型的左值引用,则程序格式错误。
这意味着,对 const volatile 限定的 T 的左值引用不能绑定到任何右值,即使它们是引用兼容的。 A b = std::move(a);显然违反了这条规则,因此它的格式不正确。
但是我不知道为什么编译std::move(a).show();而不报告错误。根据这个规则:
对于非静态成员函数,隐式对象参数的类型为
没有引用限定符或带有 & 引用限定符的函数声明的“对 cv X 的左值引用”
成员函数的隐式对象参数的类型show将为volatile const A& . 一般来说,它肯定违反了 [dcl.init.ref#5.2]。如果将成员函数的定义show改为:
void show() volatile const& {
}
Run Code Online (Sandbox Code Playgroud)
std::move(a).show();会畸形。所以必须在下面的规则中有一些魔法,std::move(a).show();在更改之前编译make show。规则是:
over.match.funcs#general-5
对于没有引用限定符声明的非静态成员函数,适用附加规则:
即使隐式对象参数不是 const 限定的,只要在所有其他方面参数可以转换为隐式对象参数的类型,右值也可以绑定到参数。
老实说,我真的不知道“在所有其他方面”这个词是什么意思?“隐式对象参数的类型”指的是什么?“类型”是指volatile const A&还是被引用的类型volatile const A?措辞非常含糊。无论如何,对 const volatile T 的左值引用不能绑定到任何类型的右值T。那么,如何解读呢?
作为对比:
#include <iostream>
struct B{
void show(){}
};
int main(){
volatile B b;
std::move(b).show(); //ill-formed
}
Run Code Online (Sandbox Code Playgroud)
的隐式对象参数的类型show将是B&,根据文献[over.match.funcs#一般-5],即使忽略const-qualifier,它仍然形成不良的,由于它丢弃volatile-qualifier。从这个例子中,它暗示,对于这句话“在所有其他方面,参数都可以转换为隐式对象参数的类型”,其中类型应该指的是引用类型而不是引用所指的类型。如果魔法是这样,它仍然不足以使std::move(a).show(); 形成良好的。
那么,如何解读这些问题呢?不知道怎么用[over.match.funcs#general-5]来解释这两个例子。
Run Code Online (Sandbox Code Playgroud)struct A { A() = default; A(volatile const A &) {} void show() const volatile {} }; int main() { volatile A a; std::move(a).show(); // OK }
show()根据[over.match.funcs]/4,成员函数的隐含对象参数是const volatile A&,为了重载决策的目的,我们可以根据[over.match.funcs]/5考虑数据成员函数为
void show(const volatile A&);
Run Code Online (Sandbox Code Playgroud)
现在,考虑到这一点,我们首先简化示例,目的是:
A可以绑定到隐含的对象参数或类型const volatile A&,但当参数用于常规自由函数时,却不能绑定到相同类型的函数参数。因此,请考虑以下简化示例:
#include <memory>
struct A {
void show() const volatile {}
};
void g(const volatile A &) { }
int main() {
volatile A a;
g(std::move(a)); // (i) Error.
std::move(a).show(); // (ii) OK.
}
Run Code Online (Sandbox Code Playgroud)
GCC (10.1.0) 中 (i) 处的错误消息是:
错误:无法将类型的非常量左值引用绑定到类型 {aka }
const volatile A&的右值std::remove_reference<volatile A&>::typevolatile A
这是根据[dcl.init.ref]/5.2所期望的(正如您自己所指出的),它不允许右值在初始化时绑定到volatile引用,即使它们是const- 限定的。
那么为什么(ii)会被接受呢?或者,相反,为什么 [dcl.init.ref]/5.2 的限制显然不适用于成员函数的隐式对象参数的类似情况?
答案在于 [over.match.funcs ]/5.1 ,其中包含未使用 ref-qualifier声明的成员函数的异常:
[over.match.funcs ]/5 [...] 对于没有引用限定符声明的非静态成员函数,适用附加规则:
- /5.1即使隐式对象参数不是 const 限定的,只要在所有其他方面可以将参数转换为隐式对象参数的类型,就可以将右值绑定到该参数。
[over.match.funcs ]/5.1 删除了 [dcl.init.ref]/5 关于右值(右值绑定)的禁止,其余标准应用于参数(右值被忽略;volatile A)是否可以(“在所有其他尊重”)转换为隐式对象参数(const volatile A&)。作为隐式对象参数,如上所示,在这种情况下始终是左值引用,这里“在所有其他方面”本质上意味着隐式对象参数是引用兼容的(根据[dcl.init.ref]/4)与(忽略右值)参数类型。
// [over.match.funcs ]/5.1 special case: rvalue prohibition waived
volatile A a; // argument: a
const volatile A& aref = a; // ok, reference-compatible
// ^^^^^^^^^^^^^^^^^ implicit object parameter
Run Code Online (Sandbox Code Playgroud)
可以说,[over.match.funcs]/5.1 可以更清楚地表明,它既适用于非const限定(通常)禁止 from-rvalue 绑定的情况,也适用于volatile-cv-qualification(通常)禁止 from-rvalue 绑定的情况。
我们最终可以查询编译器这是否实际上是它用来允许的特定规则(ii),通过显式添加&-ref 限定符,根据[over.match.funcs]/4.1将进行的更改对隐式对象参数的类型没有影响:
#include <memory>
struct A {
void show() const volatile & {}
};
void g(const volatile A &) { }
int main() {
volatile A a;
g(std::move(a)); // (i) Error.
std::move(a).show(); // (ii') Error.
}
Run Code Online (Sandbox Code Playgroud)
正如预期的那样,如果我们&向show()重载添加 - 限定符,(ii) 同样会像 (i) 一样失败,尽管会出现另一个错误消息 (GCC):
错误:传递
std::remove_reference<volatile A&>::type{akavolatile A} 作为this参数会丢弃限定符
对于这个错误,Clang (10.0.0) 可以说有更现场的错误消息:
错误:
this成员函数的参数show是右值,但函数具有非常量左值引用限定符
| 归档时间: |
|
| 查看次数: |
229 次 |
| 最近记录: |