关于被volatile限定符限定的成员函数的问题
An issue about the member function qualified by volatile qualifier
#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
}
考虑example,例子的结果超出了我对一些相关规则的理解。
对于A b = std::move(a);
,它的格式不正确,因为它违反了以下规则,即:
dcl.init.ref#5.2
Otherwise, if the reference is an lvalue reference to a type that is not const-qualified or is volatile-qualified, the program is ill-formed.
这意味着,对 const volatile 限定 T 的左值引用不能绑定到任何 rvalue,即使它们是引用兼容的。 A b = std::move(a);
显然违反了这个规则,因此它是错误的。
但是不知道为什么编译std::move(a).show();
不报错。根据这个规则:
For non-static member functions, the type of the implicit object parameter is
“lvalue reference to cv X” for functions declared without a ref-qualifier or with the & ref-qualifier
成员函数show
的隐式对象参数的类型将是volatile const A&
。总的来说,肯定违反了[dcl.init.ref#5.2]。如果把成员函数show
的定义改成:
void show() volatile const& {
}
std::move(a).show();
将是错误格式的。因此,在更改 show
之前编译 std::move(a).show();
的规则中一定有一些神奇之处。规则是:
over.match.funcs#general-5
For non-static member functions declared without a ref-qualifier, an additional rule applies:
even if the implicit object parameter is not const-qualified, an rvalue can be bound to the parameter as long as in all other respects the argument can be converted to the type of the implicit object parameter.
老实说,我真的不知道“在所有其他方面”这句话是什么意思? “隐式对象参数的类型”指的是什么? “类型”是指 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
}
show
的隐式对象参数的类型将是 B&
,根据 [over.match.funcs#general-5],即使忽略 const-qualifier
,它也是由于它丢弃了 volatile-qualifier
,所以仍然格式错误。从这个例子来看,这意味着,对于这句话“在所有其他方面,参数可以转换为隐式对象参数的类型”,其中类型应该引用 reference type 而不是比引用所指的类型。如果魔法是这样,仍然不足以使 std::move(a).show();
合式。
那么,如何解读这些问题呢?我不知道如何使用 [over.match.funcs#general-5] 来解释这两个例子。
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&
, such that for the purpose of overload resolution, we may, as per [over.match.funcs]/5,将数据成员函数视为
void show(const volatile A&);
现在,考虑到这一点,让我们首先简化示例,目的是:
- 比较为什么
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.
}
在 GCC (10.1.0) 中 (i) 处的错误消息是:
error: cannot bind non-const lvalue reference of type const volatile A&
to an rvalue of type std::remove_reference<volatile A&>::type
{aka volatile A
}
根据 [dcl.init.ref]/5.2,这是预期的(正如您自己指出的那样),它不允许右值在初始化时绑定到 volatile
引用,即使它们是 const
-合格.
那为什么(ii)被接受了?或者,相反,为什么 [dcl.init.ref]/5.2 的限制显然不适用于成员函数的隐式对象参数的类似情况?
答案就在
[over.match.funcs
]/5.1,其中包含声明为 且没有引用限定符 的成员函数的例外情况:
[over.match.funcs
]/5 [...] For non-static member functions declared without a ref-qualifier, an additional rule applies:
- /5.1 even if the implicit object parameter is not const-qualified, an rvalue can be bound to the parameter as long as in all other respects the argument can be converted to the type of the implicit object parameter.
[over.match.funcs ]/5.1 去掉了 [dcl.init.ref]/5 关于右值的禁止(右值绑定),剩下的标准适用于参数是否被忽略(右值被忽略;volatile A
) 可以("在所有其他方面")转换为隐式对象参数 (const volatile A&
)。作为隐式对象参数,如上所示,在这种情况下始终是左值引用,“在所有其他方面”这里实质上意味着隐式对象参数是 reference-compatible (根据[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
可以说 [over.match.funcs]/5.1 可以更清楚地表明它适用于非 const
限定(通常)禁止右值绑定的情况, 和 volatile
-cv-qualification(通常)禁止右值绑定。
我们最终可能会通过显式添加 &
-ref-qualifier 来查询编译器这是否实际上是它用来允许 (ii) 的特定规则,根据 [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.
}
正如预期的那样,如果我们向 show()
重载添加一个 &
限定符,(ii) 同样会像 (i) 那样失败,尽管会出现另一条错误消息 (GCC):
error: passing std::remove_reference<volatile A&>::type
{aka volatile A
} as this
argument discards qualifiers
对于这个错误,Clang (10.0.0) 可以说有一个更现场的错误信息:
error: this
argument to member function show
is an rvalue, but function has non-const lvalue ref-qualifier
#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
}
考虑example,例子的结果超出了我对一些相关规则的理解。
对于A b = std::move(a);
,它的格式不正确,因为它违反了以下规则,即:
dcl.init.ref#5.2
Otherwise, if the reference is an lvalue reference to a type that is not const-qualified or is volatile-qualified, the program is ill-formed.
这意味着,对 const volatile 限定 T 的左值引用不能绑定到任何 rvalue,即使它们是引用兼容的。 A b = std::move(a);
显然违反了这个规则,因此它是错误的。
但是不知道为什么编译std::move(a).show();
不报错。根据这个规则:
For non-static member functions, the type of the implicit object parameter is
“lvalue reference to cv X” for functions declared without a ref-qualifier or with the & ref-qualifier
成员函数show
的隐式对象参数的类型将是volatile const A&
。总的来说,肯定违反了[dcl.init.ref#5.2]。如果把成员函数show
的定义改成:
void show() volatile const& {
}
std::move(a).show();
将是错误格式的。因此,在更改 show
之前编译 std::move(a).show();
的规则中一定有一些神奇之处。规则是:
over.match.funcs#general-5
For non-static member functions declared without a ref-qualifier, an additional rule applies:
even if the implicit object parameter is not const-qualified, an rvalue can be bound to the parameter as long as in all other respects the argument can be converted to the type of the implicit object parameter.
老实说,我真的不知道“在所有其他方面”这句话是什么意思? “隐式对象参数的类型”指的是什么? “类型”是指 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
}
show
的隐式对象参数的类型将是 B&
,根据 [over.match.funcs#general-5],即使忽略 const-qualifier
,它也是由于它丢弃了 volatile-qualifier
,所以仍然格式错误。从这个例子来看,这意味着,对于这句话“在所有其他方面,参数可以转换为隐式对象参数的类型”,其中类型应该引用 reference type 而不是比引用所指的类型。如果魔法是这样,仍然不足以使 std::move(a).show();
合式。
那么,如何解读这些问题呢?我不知道如何使用 [over.match.funcs#general-5] 来解释这两个例子。
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&
, such that for the purpose of overload resolution, we may, as per [over.match.funcs]/5,将数据成员函数视为
void show(const volatile A&);
现在,考虑到这一点,让我们首先简化示例,目的是:
- 比较为什么
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.
}
在 GCC (10.1.0) 中 (i) 处的错误消息是:
error: cannot bind non-const lvalue reference of type
const volatile A&
to an rvalue of typestd::remove_reference<volatile A&>::type
{akavolatile A
}
根据 [dcl.init.ref]/5.2,这是预期的(正如您自己指出的那样),它不允许右值在初始化时绑定到 volatile
引用,即使它们是 const
-合格.
那为什么(ii)被接受了?或者,相反,为什么 [dcl.init.ref]/5.2 的限制显然不适用于成员函数的隐式对象参数的类似情况?
答案就在 [over.match.funcs ]/5.1,其中包含声明为 且没有引用限定符 的成员函数的例外情况:
[over.match.funcs ]/5 [...] For non-static member functions declared without a ref-qualifier, an additional rule applies:
- /5.1 even if the implicit object parameter is not const-qualified, an rvalue can be bound to the parameter as long as in all other respects the argument can be converted to the type of the implicit object parameter.
[over.match.funcs ]/5.1 去掉了 [dcl.init.ref]/5 关于右值的禁止(右值绑定),剩下的标准适用于参数是否被忽略(右值被忽略;volatile A
) 可以("在所有其他方面")转换为隐式对象参数 (const volatile A&
)。作为隐式对象参数,如上所示,在这种情况下始终是左值引用,“在所有其他方面”这里实质上意味着隐式对象参数是 reference-compatible (根据[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
可以说 [over.match.funcs]/5.1 可以更清楚地表明它适用于非 const
限定(通常)禁止右值绑定的情况, 和 volatile
-cv-qualification(通常)禁止右值绑定。
我们最终可能会通过显式添加 &
-ref-qualifier 来查询编译器这是否实际上是它用来允许 (ii) 的特定规则,根据 [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.
}
正如预期的那样,如果我们向 show()
重载添加一个 &
限定符,(ii) 同样会像 (i) 那样失败,尽管会出现另一条错误消息 (GCC):
error: passing
std::remove_reference<volatile A&>::type
{akavolatile A
} asthis
argument discards qualifiers
对于这个错误,Clang (10.0.0) 可以说有一个更现场的错误信息:
error:
this
argument to member functionshow
is an rvalue, but function has non-const lvalue ref-qualifier