14

在以下代码行中:

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

使用原型调用函数:

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

我收到此错误:

libcpfs/mkfs.cc:99:53:错误:从“File”类型的右值对“File&”类型的非常量引用无效初始化

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

我知道您不能const &根据标准将非常量引用 () 传递给右值。但是,MSVC 允许您执行此操作(请参阅此问题)。这个问题试图解释原因,但答案没有意义,因为他使用的是对文字的引用,这是一种极端情况,显然应该被禁止。

在给定的示例中,可以清楚地看到将发生以下事件顺序(就像在 MSVC 中一样):

  1. File的构造函数将被调用。
  2. File对和的引用blksize被压入堆栈。
  3. bootrec_reset利用file.
  4. 从 中返回后bootrec_reset,临时File对象被销毁。

有必要指出File引用必须是非常量的,因为它是文件的临时句柄,在该文件上调用非常量方法。此外,我不想传递要在那里File构造的构造函数参数bootrec_reset,我也看不出有任何理由File在调用者中手动构造和销毁对象。

所以我的问题是:

  1. 是什么证明 C++ 标准以这种方式不允许非常量引用?
  2. 如何强制 GCC 允许此代码?
  3. 即将到来的 C++0x 标准是否会改变这一点,还是新标准给我的东西在这里更合适,例如所有关于右值引用的胡言乱语?
4

7 回答 7

12

是的,普通函数不能将非常量引用绑定到临时对象——但方法可以——这一事实一直困扰着我。TTBOMK 的基本原理是这样的(来自这个 comp.lang.c++.moderated 线程):

假设你有:

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

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

如果您允许long &x参数 ofinc()绑定到long由 制成的临时副本y,那么这段代码显然不会按照您的预期执行——编译器只会默默地生成y保持不变的代码。显然,这是早期 C++ 时代常见的错误来源。

如果我设计了 C++,我的偏好是允许非常量引用绑定到临时对象,但禁止在绑定到引用时从左值自动转换为临时对象。但谁知道呢,这很可能会打开一罐不同的蠕虫……

于 2010-11-03T05:53:17.977 回答
7
  • “是什么证明 C++ 标准以这种方式不允许非常量引用?”

相反约定的实际经验,这就是事情最初的运作方式。C++ 在很大程度上是一种进化的语言,而不是一种设计的语言。很大程度上,那些仍然存在的规则被证明是有效的(尽管在 1998 年的标准化中出现了一些大的例外,例如臭名昭​​著export的 ,委员会发明而不是标准化现有实践)。

对于绑定规则,一个人不仅有 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以适当地进行 const 转换以返回其参数引用。但即使这是一个技术解决方案,也不要这样做。

  • “即将到来的 C++0x 标准是否会改变这一点,或者新标准给我的东西在这里更合适,例如所有关于右值引用的胡言乱语?”

不,没有任何改变给定代码的事情。

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

干杯&hth.,

于 2010-11-03T06:47:31.467 回答
6

即将到来的 C++0x 标准是否会改变这一点,还是新标准给我的东西在这里更合适,例如所有关于右值引用的胡言乱语?

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

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

// ...

bootrec_reset(as_lvalue(File(path, size, off)), blksize);
于 2010-11-03T06:55:34.747 回答
2
  1. 是一个相当随意的决定 - 例如,当临时对象是方法调用的主题时,允许对临时对象的非 const 引用(例如,释放向量分配的内存的“交换技巧”,std::vector<type>().swap(some_vector);
  2. 没有给临时的名字,我认为你不能。
  3. 据我所知,这条规则也存在于 C++0x 中(用于常规引用),但右值引用特别存在,因此您可以将引用绑定到临时对象 - 因此将 bootrec_reset 更改为 aFile &&应该使代码合法。
于 2010-11-03T04:00:56.637 回答
1

或者,简单地超载。

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

这是最简单的解决方案。

于 2012-10-07T12:24:35.827 回答
1

请注意,将 C++0x 称为“乱码”并不能很好地说明您的编码能力或理解该语言的愿望。

1)实际上并不是那么随意。允许非常量引用绑定到 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 右值引用。

于 2010-11-03T04:16:39.663 回答
0

如何强制 GCC 允许此代码?

如果您拥有 File 的定义,那么您可以尝试使用以下技巧:

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

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

即将到来的 C++0x 标准是否会改变这一点,还是新标准给我的东西在这里更合适,例如所有关于右值引用的胡言乱语?

如果可能的话,显然这是要走的路。

于 2015-07-11T11:29:36.767 回答