首页 > 解决方案 > 关于由 volatile 限定符限定的成员函数的问题

问题描述

#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
}

考虑这个例子,这个例子的结果超出了我对一些相关规则的理解。

对于A b = std::move(a);,它的格式不正确,因为它违反了以下规则,即:
dcl.init.ref#5.2

否则,如果引用是对非 const-qualified 或volatile-qualified类型的左值引用,则程序格式错误。

这意味着,对 const volatile-qualified 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& {

}

std::move(a).show();将是畸形的。所以在下面的规则中必须有一些魔法,std::move(a).show();在改变之前编译show。规则是:
over.match.funcs#general-5

对于没有 ref-qualifier 声明的非静态成员函数,附加规则适用:

即使隐式对象参数不是 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
}

的隐式对象参数的类型show将是B&,根据 [over.match.funcs#general-5],即使 ignore const-qualifier,它仍然是不正确的,因为它丢弃了volatile-qualifier. 从这个例子可以看出,对于这句话“在所有其他方面,参数都可以转换为隐式对象参数的类型”,其中类型应该引用引用类型而不是引用引用的类型。如果魔力是这样的话,还不足以让其std::move(a).show(); 形成良好的形态。

那么,如何解读这些问题呢?我不知道如何使用 [over.match.funcs#general-5] 来解释这两个示例。

标签: c++language-lawyer

解决方案


struct A {
    A() = default;
    A(volatile const A &) {}
    void show() const volatile {}
};

int main() {
    volatile A a;
    std::move(a).show(); // OK
}

根据[over.match.funcs]/4,成员函数的隐含对象参数show()是,这样,为了重载解决方案,我们可以根据[over.match.funcs]/5考虑数据成员函数为const volatile A&

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) 处的错误消息是:

错误:无法将类型的非 const 左值引用绑定到 {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

可以说,[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):

错误:std::remove_reference<volatile A&>::type{aka volatile A} 作为this参数传递会丢弃限定符

对于这个错误,Clang (10.0.0) 可以说有一个更现场的错误消息:

错误: this成员函数的参数show是右值,但函数具有非 const 左值引用限定符


推荐阅读