为什么const volatile引用不能绑定到右值引用?

13

我希望了解为什么const volatile引用不能绑定到右值引用?禁止这种转换的合理原因是什么?

在下面的代码中,我注释掉了无法编译的行:

int main(){
 int i=1;
 //const volatile int& cvi2=std::move(i); -> ERROR: why?
 const volatile int i2=0;
 //const volatile int& cvi3=std::move(i2);// -> ERROR: why?
}

这里有一个更现实的场景,因类似原因而无法编译:
#include<iostream>
template<class T> void g(const T& b){
  //do usefull things
}
template<class T,class F> void f(T& a,F a_func){
  //do usefull things with a
  a_func(std::move(a));
}
int main(){
   int i=0;
   volatile int vi=1;
   f(i,g<int>); //OK no error;
   f(vi,g<volatile int>);//ERROR: can not convert volatile int to
                                 //const volatile int &
 }

在这段代码中,我本来期望g<volatile int>(const volatile&)可以接受任何参数。
下面是一个更具体的示例:
#include <vector>
using usefull_type=int;
void set_communication_channel_to(volatile usefull_type* g,size_t n);
int main(){
  auto vect=
    std::vector<volatile usefull_type>(10,usefull_type{42});//->ERROR no known conversion
                                                      // from int to const volatile int &
  set_communication_channel_to(vect.data(),vect.size());
  //... 
  //...
 }

这个限制肯定有很好的理由,不是吗?

const volatile 这个是什么意思? - πάντα ῥεῖ
4
所有阅读都是可观察的行为吗? - Yakk - Adam Nevraumont
你能否只发布最少的代码行以产生你误解的错误,不包含注释? - kabanus
我的猜测是,如果您试图将const volatile ref绑定到rvalue,则可能正在做一些错误的事情。因此,标准出于安全原因禁止这样做。 volatile表示该值可能会被外部原因(如其他线程)改变,这与只能将常量引用绑定到临时对象的规则相矛盾。 - Emerald Weapon
Leon理解了我的问题。事实上,我在玩弄函数重载时发现了这个限制。我猜我们可以通过一些连续的模板实例化来实现这样的绑定。我会尝试找到一个例子。 - Oliv
显示剩余8条评论
2个回答

11
正确的问题应该是:“为什么const volatile引用不能绑定到rvalue?”即使没有直接涉及rvalue引用,下面的代码也无法编译:
const volatile int& cvr = 0;

这个答案引用了标准的相关部分:

根据 [dcl.init.ref]/5,要通过将右值绑定到引用来初始化引用,该引用必须是一个非volatileconst左值引用或一个右值引用:

— 否则,该引用应该是指向非易失性常量类型的左值引用(即,cv1应为const),或者该引用应该是右值引用。

我猜测这个限制在C++98标准中有历史渊源,因为rvalue被限制为临时对象,完全由编译器管理。编译器可以将临时对象放置在任何地址或寄存器中,并将其视为具有可观察读取的易失性对象是没有意义的。在新的标准中,左值引用可以通过std::move()转换为右值引用,但结果是它获得了旧的rvalue属性,即它们的确切内存地址不重要,因此不能将易失性属性分配给它。

从技术上讲,这个限制并不是一个非常限制性的限制,因为您可以通过额外的间接级别有效地将const volatile引用绑定到右值:

// This doesn't compile
// const volatile int& cvr = 0;

// This does compile
const int& cr = 0;
const volatile int& cvr = cr;

这实际上是我的问题。我应该编辑一下来改变它吗? - Oliv
@Oliv 我认为你至少应该编辑一下标题。我会相应地更新我的答案。 - Leon
我做到了,我保留了“绑定到右值引用”的说法,因为这实际上是我的问题的正确措辞:您已经重新表述并概括了任何类型的 rvalue(ref 或非 ref)的问题。因此,我只是尝试挑选您答案中的最小部分,以使我的问题易于理解。 - Oliv

5
文字上的原因是[dcl.init.ref]禁止这样的声明:
引用类型“cv1 T1”通过类型为“cv2 T2”的表达式进行初始化,如下所示: - 如果引用是左值引用并且初始化器表达式[...], - 否则,该引用应为对非易失性const类型的左值引用(即,cv1应为const),或者该引用应为右值引用。
更深层次的原因只是一个猜想(以及提问为什么的问题):这样的声明毫无意义。使用volatile的目的是使所有读写行为可观察。如果您从临时对象初始化到const volatile的引用,则现在您是此对象的所有者。但是您不能写入它。也没有其他人可以。那么volatile传达了什么?

是的,基本上和我在评论中提到的一样。如果你真的想要,你仍然可以使用const volatile T&&,这使得事情更加对称。 - Emerald Weapon
2
@M.M 关键是这种特殊用例不可能适用于编译器从虚空中召唤出来的临时内存。 - T.C.
2
@M.M:它不会是一个只读但可更改的位置,也不会是一个内存映射的只读硬件寄存器。它是在那一行通过复制临时创建的。它仅存在于C++程序中。volatile可能对它被复制的对象有意义,但不适用于副本。我不知道如何向您解释得更清楚了。 - Lightness Races in Orbit
1
@LightnessRacesinOrbit 我也不知道该如何更好地解释我认为很简单的概念,也许我们只能在这里结束这个“讨论”了。 - M.M
2
@M.M:你的损失。 :) 其他人都明白。 - Lightness Races in Orbit
显示剩余13条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接