一个有趣的问题:临时变量能否为l-value,或者,能否有non-const引用?

singlerace 2003-01-12 10:31:15
前几天碰到这个问题,考虑以下代码:

class bar{};
void foo(const bar&) {}
void foo(bar&) {}

void main()
{
foo(bar());
}
foo(bar())会调用哪个函数?
我分别在vc 6.0 sp3, vc.net 7.0和vc.net 7.1 final beta上作了试验,结果很有趣。
vc6和vc70的结果一样:在foo(const bar&)和foo(bar&)都存在时,编译器选择foo(bar&),并给出一个level 4警告:
"warning C4239: nonstandard extension used : 'argument' : conversion from 'class bar' to 'class bar &'
A reference that is not to 'const' cannot be bound to a non-lvalue"
如果注释掉foo(bar&),则编译器选择foo(const bar&)且没有任何警告。
在vc7.1下,如果两者都存在,编译器选择foo(const bar&);如果注释掉foo(const bar&),则选择foo(bar&)并给出上面的警告。
msdn对warning C4239的解释是:
"nonstandard extension used : 'token' : conversion from 'type' to 'type'
This type conversion is not allowed by the C++ draft standard, but it is permitted here as an extension.
This warning is always followed by at least one line of explanation describing the language rule being violated."
看起来C++标准草案认为non-lvalue不应该有non-const引用,至少认为对non-lvalue的non-const引用是不安全的。
换句话说,C++似乎认为临时变量是non-lvalue。
再来看这行代码:
bar() = bar();
这行代码在三个vc版本中都能编译通过,没有任何警告。这是否说明临时变量可以是l-value?
看起来,C++标准草案虽然不允许,但microsoft的人认为临时变量为l-value是有一定合理性的,因此作了扩展。
但是在vc6和vc7.0中,在const引用和non-const引用都存在的情况下,编译器对临时变量却优先选择了non-const引用;vc7.1的选择似乎更合理些--他优先选择了non-const引用。

如果你也这么认为,那麻烦又来了。看看这句:
std::cout << reinterpret_cast<std::ostringstream&>( std::ostringstream() << "X" ).str();
猜猜cout会输出什么?在vc71中,输出的不是字符串"X",而是一个内存地址!
原因是编译器选择了成员操作符ostream& ostream::operator<<(const void*), 而不是非成员操作符ostream operator<<( ostream&, const char* )。
因为如果选择非成员操作符就意味着对临时变量std::ostringstream()产生一个non-const引用。结果就是,在vc71中,const char*被解释为const void*!
更让人疯狂的是这句:
std::ostringstream() << "X" << "X";
第一个调用成员操作符ostream::operator<<(const void*),第二个调用非成员操作符operator<<(ostream&,const char*)
这就是我前几天在一个项目中碰到的情况:vc6和vc70中正常工作的代码,用vc71编译后本来应该输出字符串的地方却输出了内存地址。
当然解决方法也是有的,比如,可以这么写:
const_cast<std::ostringstream&>( std::ostringstream() ) << "X";
我目前的观点是,vc71的编译器的选择应该是正确的,但是stream library的实现有问题,不知道大家对这个问题怎么看。
如果认为stream library有问题的话,麻烦就大了,我看了一下stlport 4.5.3,发现也有这个问题。欢迎大家讨论。
另外,我目前手头没有其他编译器,不知道其他编译器是怎么处理这个问题的。
...全文
314 35 打赏 收藏 转发到动态 举报
写回复
用AI写文章
35 条回复
切换为时间正序
请发表友善的回复…
发表回复
singlerace 2003-01-14
  • 打赏
  • 举报
回复
本来想结帖了,没想到大家的意见这么不统一。
以前没提问过,怎么继续往帖里加分?
singlerace 2003-01-14
  • 打赏
  • 举报
回复
to deanjiang(dean):
>临时变量本质上是一个右值,换句话说,应当把他理解为一个数值,而不是一个对象
这句完全不能同意,临时变量当然可以是左值。难道你认为bar()也是一个数值?
> int i=3+2;
这里没有产生临时变量,3+2的结果在编译期就求出来了。

> 临时对象可以调用非const修饰的成员函数
这确实可以,但说是好处,我看未必,我认为是他把临时变量看成右值后的一个弥补措施。其实他们的语义是矛盾的:
struct A
{
A& l_value() {return *this;}
};

A().l_value();
A()为右值,调用成员函数后,得到了左值。
singlerace 2003-01-14
  • 打赏
  • 举报
回复
结帖了。有兴趣可以看看这些讨论:

http://groups.google.com/groups?hl=zh-CN&lr=&ie=UTF-8&oe=UTF-8&threadm=1id02v03u2qj55clrojpihjtmtk1edaphj%404ax.com&rnum=1&prev=/groups%3Fq%3Dsting%2Bdoug%2Bl-value%26hl%3Dzh-CN%26lr%3D%26ie%3DUTF-8%26oe%3DUTF-8%26selm%3D1id02v03u2qj55clrojpihjtmtk1edaphj%25404ax.com%26rnum%3D1

http://groups.google.com/groups?selm=77q8ju8cqfg11td4qnn24i9unqp54801in%404ax.com
widewave 2003-01-14
  • 打赏
  • 举报
回复
临时变量这个概念有点模糊。
如果要定义为中间变量,我想只能是右值。
被异常抛出的变量,它的生存期很长,不是中间变量,可以是左值。
earthharp 2003-01-14
  • 打赏
  • 举报
回复
管理里好象可以加分的。
这个问题上意见不统一比较正常。C++标准本来就没有进行有力的约束。而且现在的编译器也实现的并不统一。
ToUpdate 2003-01-14
  • 打赏
  • 举报
回复
up
deanjiang 2003-01-13
  • 打赏
  • 举报
回复
对你们前面的现象,标准中的解释是临时对象可以调用非const修饰的成员函数。
就是说
struct A
{
non_const_func();
const_func()const;
};

可以这样调用:A().non_const_func();
至于这两个规定的好处,自己慢慢体会吧
earthharp 2003-01-13
  • 打赏
  • 举报
回复
Definition: C and C++ have the notion of lvalues and rvalues associated with variables and constants. The rvalue is the data value of the variable, that is, what information it contains. The "r" in rvalue can be thought of as "read" value. A variable also has an associated lvalue. The "l" in lvalue can be though of as location, meaning that a variable has a location that data or information can be put into. This is contrasted with a constant. A constant has some data value, that is an rvalue. But, it cannot be written to. It does not have an lvalue.

Another view of these terms is that objects with an rvalue, namely a variable or a constant can appear on the right hand side of a statement. They have some data value that can be manipulated. Only objects with an lvalue, such as variable, can appear on the left hand side of a statement. An object must be addressable to store a value.

结论: 临时变量为non-modifiable Lvalue, ref为modifiable Lvalue;
const 变量均为non-modifiable Lvalue.

deanjiang 2003-01-13
  • 打赏
  • 举报
回复
临时变量本质上是一个右值,换句话说,应当把他理解为一个数值,而不是一个对象;只是在计算机中,值也是要找一个地方存放的,所以有一些技巧可以修改临时变量。
int i=3+2;
由谁会认为临时变量5是一个左值呢?
singlerace 2003-01-13
  • 打赏
  • 举报
回复
看看这个:
http://wwwold.dkuug.dk/JTC1/SC22/WG21/docs/cwg_defects.html#177

177. Lvalues vs rvalues in copy-initialization
Section: 8.5 dcl.init Status: DR Submitter: Steve Adamczyk Date: 25 October 1999

[Moved to DR at 4/02 meeting.]

Is the temporary created during copy-initialization of a class object treated as an lvalue or an rvalue? That is, is the following example well-formed or not?

struct B { };
struct A {
A(A&); // not const
A(const B&);
};
B b;
A a = b;

According to 8.5 dcl.init paragraph 14, the initialization of a is performed in two steps. First, a temporary of type A is created using A::A(const B&). Second, the resulting temporary is used to direct-initialize a using A::A(A&).

The second step requires binding a reference to non-const to the temporary resulting from the first step. However, 8.5.3 dcl.init.ref paragraph 5 requires that such a reference be bound only to lvalues.

It is not clear from 3.10 basic.lval whether the temporary created in the process of copy-initialization should be treated as an lvalue or an rvalue. If it is an lvalue, the example is well-formed, otherwise it is ill-formed.

Proposed resolution (04/01):

In 8.5 dcl.init paragraph 14, insert the following after "the call initializes a temporary of the destination type":

The temporary is an rvalue.
In 15.1 except.throw paragraph 3, replace

The temporary is used to initialize the variable...
with

The temporary is an lvalue and is used to initialize the variable...
(See also issue 84.)
singlerace 2003-01-13
  • 打赏
  • 举报
回复
>但是并不会调用它!
这个我同意。

刚才在两个版本的gcc下试了一下(redhat自带的gcc 3.2和安装在cygnwin下的gcc 3.04 for win32)。gcc选择foo(const bar&),如果只有foo(bar&),gcc编译出错:
In function 'int main()': could not convert '{}' to 'bar&' in passing argument 1 of 'void foo(bar&)'
singlerace 2003-01-13
  • 打赏
  • 举报
回复
说实话,在碰到这个问题之前,我对临时变量的引用问题基本上没什么了解,从直觉上来说,临时变量除了没有名字,和局部变量不应该有任何区别。从技术角度讲,临时变量当然可以为左值,当然可以有non-const引用,但是为什么C++的标准
不允许它有non-const引用,这背后经过了什么样的思考过程,这是我感兴趣的。
经过这两天的讨论,看了一些以前的讨论,我想我大概清楚了。起因大概是这样的:
class Integer {
int n;
public:
Integer(int x) : n(x) {}
Integer& operator=(int x) {n = x; return *this};
};
void SetToZero(Integer& value) {value = 0;}
int x = 5;
// A temporary Integer is created and bound to a reference
SetToZero(x);
cout << x; // Surprise: x is still 5

上面这个例子,,在老的C++中,如果允许临时变量有non-const引用,SetToZero(x)将产生一个Integer对象并绑定一个non-const引用,于是得到错误的结果。基于这种危险性,C++就禁止临时变量有non-const引用,这其实是为了解决一个问题而产生了新的问题。
在新的C++里其实没有这个问题,B.J.的书里说得很清楚(TCPL,特别影印版P98 ):
The initializer for a "plain" T& must be an lvalue of type T.
就是说,只有确切的类型T才能初始化T&,不能经过隐式转换。因此上述代码会得到一个编译错误:
error C2664: 'SetToZero' : cannot convert parameter 1 from 'int' to 'class Integer &' A reference that is not to 'const' cannot be bound to a non-lvalue

>生命周期不可知
这点不太理解?临时变量的生命周期当然是可知的:他在你声明的时候构造,在完整的表达式结束时析构(如果没有绑定到某个引用的话),我利用的正是它的这一点。

to solotony(solotony):
>而我们为了少写一点代码(少定义一个变量名)而使用临时变量的这种习性
>(反正它很快会自动消失),导致了这种错误.
我并不是为了少些一点代码而使用临时变量,是经过仔细考虑了的(那时并不知道C++规定临时变量不能绑定到non-const引用,不过即使这样也不影响我的设计)。我是用临时变量在完整表达式结束时析构的特性来实现一个线程安全的、方便灵活且容易扩展的日志记录模块,如果你有兴趣我可以另外开贴讨论。

to anrxhzh(百宝箱):
std::ostringstream() << "X" << "X";
我知道为什么第一个<<是const void*,第二个式<<const char*。我是对VC的做法不满,VC.NET 7.1向C++标准靠拢当然是好事,但是他改变了一个特性,破坏了我的原有代码,连警告都没给一个。
> 我把reinterpret_cast改为dynamic_cast
原来的是static_cast不是reinterpret_cast。用dynamic_cast其实没有必要,static_cast就够了。

solotony(solotony)说:临时变量当然是左值,所以它可以被引用
anrxhzh(百宝箱):虽然临时对象是右值,但是有些情况下右值也是可以被修改的
我想是否这样说合适些:从本质上来说,临时变量是左值,由于C++担心临时变量在使用时容易出现问题,人为的规定临时变量为右值。
我举一个临时变量可以为左值的例子,这是今天在新闻组里看到的:
int main()
{
try {
throw 42;
} catch (int& i) {
}
}
throw产生一个临时变量,被catch(int&)捕获,这个临时变量必须是左值。
widewave 2003-01-13
  • 打赏
  • 举报
回复
“如果类中没有实现operator=,编译器会给你生成一个(如果可以的话)。”

但是并不会调用它!
anrxhzh 2003-01-13
  • 打赏
  • 举报
回复
8.5.3
singlerace 2003-01-13
  • 打赏
  • 举报
回复
earthharp(骄傲的石头):
如果我没记错的话,C++的标准说临时变量不能绑定non-const ref。
vc6和vc7.0都优先选择foo(bar&),并给出一个level4警告。
vc7.1优先选择foo(const bar&),至少final beta版如此,这是更符合C++标准的。
但是这样确实会使很多代码不能正常工作,我那个stringstream() << "X";就是这样,在vc6和vc70下好好的,到7.1下就不对了,甚至编译警告都没给一个。我是觉得7.1这一点欠妥。
singlerace 2003-01-13
  • 打赏
  • 举报
回复
widewave(冯雨):
如果类中没有实现operator=,编译器会给你生成一个(如果可以的话)。
但是如果你明确的禁止了operator=,编译就通不过了。
coader 2003-01-13
  • 打赏
  • 举报
回复
收了
widewave 2003-01-13
  • 打赏
  • 举报
回复
bar() = bar();

如果类中没有实现 operator = 的话,并不会调用 operator = 。
widewave 2003-01-13
  • 打赏
  • 举报
回复
有道理!
tomatopj 2003-01-13
  • 打赏
  • 举报
回复
请问一下讲这个的在标准的哪一节?
我看了12.2中没有说道这个问题
加载更多回复(15)

24,854

社区成员

发帖
与我相关
我的任务
社区描述
C/C++ 工具平台和程序库
社区管理员
  • 工具平台和程序库社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧