在C ++中将非const引用传递给rvalues

时间:2010-11-03 03:43:58

标签: c++ gcc c++11 rvalue-reference

在以下代码行中:

bootrec_reset(File(path, size, off), blksize);

用原型调用函数:

static void bootrec_reset(File &file, ssize_t blksize);

我收到此错误:

  

libcpfs / mkfs.cc:99:53:错误:“File&”类型的非const引用的初始化无效来自'文件'类型的右值

     

libcpfs / mkfs.cc:30:13:错误:传递'void bootrec_reset(File&,ssize_t)'的参数1

我知道你不能根据标准将非const引用(const &)传递给rvalues。但是,MSVC允许您这样做(参见this question)。 This question试图解释原因,但答案没有意义,因为他正在使用文字引用,这是一个极端的案例,显然是不允许的。

在给定的示例中,很明显会发生以下事件顺序(就像在MSVC中一样):

  1. File的构造函数将被调用。
  2. Fileblksize的引用将被推送到堆栈中。
  3. bootrec_reset使用file
  4. bootrec_reset返回后,临时File将被销毁。
  5. 有必要指出File引用需要是非const的,因为它是文件的临时句柄,在其上调用非const方法。此外,我不想将File的构造函数参数传递给bootrec_reset来构造那里,我也没有看到任何理由在调用者中手动构造和销毁File对象

    所以我的问题是:

    1. C ++标准以这种方式禁止非const引用的理由是什么?
    2. 如何强制GCC允许此代码?
    3. 即将推出的C ++ 0x标准是否会改变这种情况,或者新标准是否为我提供了更合适的内容,例如所有关于rvalue引用的乱码?

7 个答案:

答案 0 :(得分:8)

是的,普通函数不能将非const引用绑定到temporaries - 但是方法可以 - 这一事实总是让我烦恼。 TTBOMK的理由是这样的(来自this comp.lang.c++.moderated thread):

假设你有:

 void inc( long &x ) { ++x; }

 void test() {
     int y = 0;
     inc( y );
     std::cout << y;
 } 

如果您允许long &x的{​​{1}}参数绑定到由inc()制作的临时long副本,则此代码显然不会按预期执行 - 编译器只会静默生成保持y不变的代码。显然,这是C ++早期的常见错误来源。

如果我设计了C ++,我的偏好是允许非const引用绑定到temporaries,但是当绑定到引用时禁止从lvalues到temporaries的自动转换。但谁知道,这可能会开辟出一种不同的蠕虫......

答案 1 :(得分:6)

  • “C ++标准以这种方式禁止非const引用的理由是什么?”

相反惯例的实践经验,这是事情最初的运作方式。 C ++在很大程度上是一种进化的语言,而不是一种设计的语言。很大程度上,那些仍然存在的规则是有效的(虽然1998年的标准化发生了一些例外,例如臭名昭​​着的export,委员会发明了而不是标准化现有的做法。) p>

对于绑定规则,我不仅具有C ++的经验,而且还具有与Fortran等其他语言相似的经验。

正如@j_random_hacker在他的回答中指出的那样(正如我写的那样得分为0,表明SO中的得分确实不能作为衡量质量的标准),最严重的问题与隐式转换和超载有关分辨率。

  • “我如何强制GCC允许此代码?”

你不能。

而不是......

bootrec_reset(File(path, size, off), blksize);

...写...

File f(path, size, off);
bootrec_reset(f, blksize);

或者定义bootrec_reset的适当重载。或者,如果“聪明”代码有吸引力,您原则上可以编写bootrec_reset(tempref(File(path, size, off)), blksize);,您只需定义tempref以适当地返回其参数引用。但即使这是一种技术解决方案,也不要。

  • “即将推出的C ++ 0x标准是否会改变这一点,或者新标准是否会让我更合适,例如所有关于右值引用的乱码?”

不,没有任何改变给定代码的东西。

但是,如果您愿意重写,那么您可以使用例如C ++ 0x右值引用,或上面显示的C ++ 98变通方法。

干杯&amp;第h。,

答案 2 :(得分:6)

  

即将推出的C ++ 0x标准是否会改变这一点,或者新标准是否提供了更合适的内容,例如所有关于rvalue引用的乱码?

是。由于每个名称都是左值,因此将任何表达式视为左值几乎是微不足道的:

template <typename T>
T& as_lvalue(T&& x)
{
    return x;
}

// ...

bootrec_reset(as_lvalue(File(path, size, off)), blksize);

答案 3 :(得分:2)

  1. 是一个相当随意的决定 - 例如,当临时是方法调用的主题时,允许对临时值的非常量引用(例如,“交换技巧”释放由向量分配的内存,{{1} })
  2. 不给临时名字,我认为你不能。
  3. 据我所知,此规则也存在于C ++ 0x中(对于常规引用),但rvalue引用具体存在,因此您可以绑定对临时引用的引用 - 因此更改bootrec_reset以获取std::vector<type>().swap(some_vector);应该使代码合法。

答案 4 :(得分:1)

请注意,调用C ++ 0x“jibberish”并不能很好地描述您的编码能力或理解该语言的愿望。

1)实际上并非如此武断。允许非const引用绑定到r值会导致极其混乱的代码。我最近提交了一个针对MSVC的错误,该错误与此相关,其中非标准行为导致符合标准的代码无法使用异常行为进行编译和/或编译。

在您的情况下,请考虑:

#include <iostream>

template<typename T>
void func(T& t)
{
    int& r = t;
    ++r;
}

int main(void)
{
    int i = 4;
    long n = 5;
    const int& r = n;

    const int ci = 6;
    const long cn = 7;

    //int& r1 = ci;
    //int& r2 = cn;

    func(i);
    //func(n);

    std::cout << r << std::endl;
}

您要编译哪条注释行?您是否希望func(i)更改其参数而func(n)不更改?

2)你无法编译代码。您不希望拥有该代码。未来版本的MSVC可能会删除非标准扩展,并且无法编译该代码。相反,使用局部变量。您总是可以使用额外的一对大括号来控制该局部变量的生命周期,并使其在下一行代码之前被销毁,就像临时变量一样。或r值参考。

{
  File ftemp(path, size, off);
  bootrec_reset(ftemp, blksize);
}

3)是的,您可以在此方案中使用C ++ 0x r值引用。

答案 5 :(得分:1)

或者,只是过载。

static void bootrec_reset(File &&file, ssize_t blksize) {
    return bootrec_reset(file, blksize);
}

这是最简单的解决方案。

答案 6 :(得分:0)

  

如何强制GCC允许此代码?

如果您拥有File的定义,那么您可以尝试玩这样的技巧:

class File /* ... */ {
public:
  File* operator&() { return this; }
/* ... */
};
/* ... */
bootrec_reset(*&File(path, size, off), blksize);

这在c ++ 98模式下为我编译。

  

即将推出的C ++ 0x标准是否会改变这一点,或者新标准是否提供了更合适的内容,例如所有关于rvalue引用的乱码?

显然,如果可能的话,这是一条路。