在 C++ 中将非常量引用传递给右值;

发布于 2024-09-30 13:01:12 字数 1330 浏览 9 评论 0 原文

在以下代码行中:

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

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

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

  1. 将调用 File 的构造函数。
  2. Fileblksize 的引用被推送到堆栈上。
  3. bootrec_reset 利用file
  4. bootrec_reset返回后,临时File被销毁。

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

所以我的问题是:

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

In the following line of code:

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

Calling a function with prototype:

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

I receive this error:

libcpfs/mkfs.cc:99:53: error: invalid initialization of non-const reference of type 'File&' from an rvalue of type 'File'

libcpfs/mkfs.cc:30:13: error: in passing argument 1 of 'void bootrec_reset(File&, ssize_t)'

I'm aware that you can not pass non-const references (const &) to rvalues according to the standard. MSVC however allows you to do this (see this question). This question attempts to explain why but the answer makes no sense as he is using references to literals, which are a corner case and should obviously be disallowed.

In the given example it's clear to see that following order of events will occur (as it does in MSVC):

  1. File's constructor will be called.
  2. A reference to the File, and blksize, are pushed on the stack.
  3. bootrec_reset makes use of file.
  4. After returning from bootrec_reset, the temporary File is destroyed.

It's necessary to point out that the File reference needs to be non-const, as it's a temporary handle to a file, on which non-const methods are invoked. Furthermore I don't want to pass the File's constructor arguments to bootrec_reset to be constructed there, nor do I see any reason to manually construct and destroy a File object in the caller.

So my questions are:

  1. What justifies the C++ standard disallowing non-const references in this manner?
  2. How can I force GCC to permit this code?
  3. Does the upcoming C++0x standard change this in anyway, or is there something the new standard gives me that is more appropriate here, for example all that jibberish about rvalue references?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

自由如风 2024-10-07 13:01:12

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

假设您有:

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

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

如果您允许 inc()long &x 参数绑定到临时 y 生成的 >long 副本,这段代码显然不会执行您所期望的操作 - 编译器只会默默地生成使 y 保持不变的代码。显然,这是早期 C++ 时代常见的错误来源。

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

Yes, the fact that plain functions cannot bind non-const references to temporaries -- but methods can -- has always bugged me. TTBOMK the rationale goes something like this (sourced from this comp.lang.c++.moderated thread):

Suppose you have:

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

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

If you allowed the long &x parameter of inc() to bind to a temporary long copy made from y, this code obviously wouldn't do what you expect -- the compiler would just silently produce code that leaves y unchanged. Apparently this was a common source of bugs in the early C++ days.

Had I designed C++, my preference would have been to allow non-const references to bind to temporaries, but to forbid automatic conversions from lvalues to temporaries when binding to references. But who knows, that might well have opened up a different can of worms...

乖乖公主 2024-10-07 13:01:12
  • “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 解决方法。

干杯&呵呵,

  • "What justifies the C++ standard disallowing non-const references in this manner?"

Practical experience with the opposite convention, which was how things worked originally. C++ is to a large degree an evolved language, not a designed one. Largely, the rules that are still there are those that turned out to work (although some BIG exceptions to that occurred with the 1998 standardization, e.g. the infamous export, where the committee invented rather than standardizing existing practice).

For the binding rule one had not only the experience in C++, but also similar experience with other languages such as Fortran.

As @j_random_hacker notes in his answer (which as I wrote this was scored 0, showing that the scoring in SO really doesn't work as a measure of quality), the most serious problems have to do with implicit conversions and overload resolution.

  • "How can I force GCC to permit this code?"

You can't.

Instead of ...

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

... write ...

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

Or define an appropriate overload of bootrec_reset. Or, if "clever" code appeals, you can in principle write bootrec_reset(tempref(File(path, size, off)), blksize);, where you simply define tempref to return its argument reference appropriately const-casted. But even though that's a technical solution, don't.

  • "Does the upcoming C++0x standard change this in anyway, or is there something the new standard gives me that is more appropriate here, for example all that jibberish about rvalue references?"

Nope, nothing that changes things for the given code.

If you're willing to rewrite, however, then you can use e.g. C++0x rvalue references, or the C++98 workarounds shown above.

Cheers & hth.,

风筝有风,海豚有海 2024-10-07 13:01:12

即将推出的 C++0x 标准是否会改变这一点,或者新标准是否为我提供了更合适的东西,例如所有关于右值引用的胡言乱语?

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

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

// ...

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

Does the upcoming C++0x standard change this in anyway, or is there something the new standard gives me that is more appropriate here, for example all that jibberish about rvalue references?

Yes. Since every name is an lvalue, it is almost trivial to treat any expression as if it was an lvalue:

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

// ...

bootrec_reset(as_lvalue(File(path, size, off)), blksize);
后来的我们 2024-10-07 13:01:12
  1. 是一个相当任意的决定 - 例如,当临时对象是方法调用的主题时,允许对临时对象进行非常量引用(例如,用于释放向量分配的内存的“交换技巧”,std::vector< ;type>().swap(some_vector);)
  2. 如果没有给临时变量一个名称,我认为你不能。
  3. 据我所知,这条规则也存在于 C++0x 中(对于常规引用),但是右值引用特别存在,因此您可以将引用绑定到临时对象 - 因此将 bootrec_reset 更改为采用 File &&< /code> 应该使代码合法。
  1. Is a fairly arbitrary decision - non-const references to temporaries are allowed when the temporary is the subject of a method call, for example (e.g. the "swap trick" to free the memory allocated by a vector, std::vector<type>().swap(some_vector);)
  2. Short of giving the temporary a name, I don't think you can.
  3. As far as I'm aware this rule exists in C++0x too (for regular references), but rvalue references specifically exist so you can bind references to temporaries - so changing bootrec_reset to take a File && should make the code legal.
×眷恋的温暖 2024-10-07 13:01:12

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

1)其实并不是那么随意。允许非常量引用绑定到右值会导致代码极其混乱。我最近针对 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 右值引用。

Please note that calling C++0x "jibberish" is not presenting a very favorable picture of your coding ability or desire to understand the language.

1) Is actually not so arbitrary. Allowing non-const references to bind to r-values leads to extremely confusing code. I recently filed a bug against MSVC which relates to this, where the non-standard behavior caused standard-compliant code to fail to compile and/or compile with a deviant behavior.

In your case, consider:

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

Which of the commented lines to you want to compile? Do you want func(i) to change its argument and func(n) to NOT do so?

2) You can't make that code compile. You don't want to have that code. A future version of MSVC is probably going to remove the non-standard extension and fail to compile that code. Instead, use a local variable. You can always use a extra pair of braces to control the lifetime of that local variable and cause it to be destroyed prior to the next line of code, just like the temporary would be. Or r-value references.

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

3) Yes, you can use C++0x r-value references in this scenario.

戈亓 2024-10-07 13:01:12

或者,简单地超载。

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

这是最简单的解决方案。

Alternatively, simply overload.

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

This is the easiest solution.

初心未许 2024-10-07 13:01:12

如何强制 GCC 允许此代码?

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

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

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

即将推出的 C++0x 标准是否会改变这一点,或者新标准是否为我提供了更合适的东西,例如所有关于右值引用的胡言乱语?

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

How can I force GCC to permit this code?

If you own the definition of File then you can try playing tricks such as this one:

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

This compiles for me in c++98 mode.

Does the upcoming C++0x standard change this in anyway, or is there something the new standard gives me that is more appropriate here, for example all that jibberish about rvalue references?

Obviously this the way to go if at all possible.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文