2009-05-22

modifyできるrvalue

C++03 の規格, 3.10 Lvalues and rvalues を読んで、その解釈を書いてみる。

2 An lvalue refers to an object or function. Some rvalue expressions—those of class or cv-qualified class type—also refer to objects.47)

47) Expressions such as invocations of constructors and of functions that return a class type refer to objects, and the implementation can invoke a member function upon such objects, but the expressions are not lvalues.

5 The result of calling a function that does not return a reference is an rvalue. User defined operators are functions, and whether such operators expect or yield lvalues is determined by their parameter and return types.

10 An lvalue for an object is necessary in order to modify the object except that an rvalue of class type can also be used to modify its referent under certain circumstances. [Example: a member function called for an object (9.3) can modify the object. ]

Based on above rules, i wrote following codes.

struct Foo
{
        //non-static non-mutable member variable.
        int x ;

        // non-static non-const member function.
        void func()
        {
                // modify a member variable.
                x = 0 ;
        }
} ;

// result of calling this function shall be a rvalue
// since it does not return Foo as reference.
Foo f( ) { return Foo() ; }

void g()
{
        // according to 47), 
        // it allows to call member function upon a rvalue
        // which is a result of "Expressions such as invocations of constructors and of functions"
        // it does not mentions about wheather "member function" is cv-qualifier or not.
        // and because of 3.10 p10, member function can modify it's member
        // even if it's called upon a rvalue "under certain circumstances"(see above)
        // so following two lines shall be well-formed.

        Foo().func() ; // #1 OK. 
        f().func() ; // #2 OK.

        // and following two line shall be ill-formed.
        // because 3.10 p10 prohibit modifying a object of rvalue.

        Foo().x = 0 ; // #3 Error
        f().x = 0 ; // #4 Error
}

VC9での結果は、私の理解通りにはならなかった。。#1, #2は、期待通りに、コンパイルが通る。#3は、期待通りにコンパイルエラーになる。ところが、#4は通ってしまう。3.10で言及しているのは、コンストラクタや関数の戻り値のrvalueのオブジェクトに大して、メンバ関数が呼べ、かつ、そのメンバ関数の中ではmodifyできるということだけで、それ以外については、modifyするにはlvalueでなければならないとしているのに、不思議だ。

しかし、rvalue referenceをサポートしているコンパイラでは、f()の戻り値を Foo && で受ければ、modifyできるんじゃなかろうか。私の理解する限りでは。

Foo && ref = f() ;
ref.x = 0 ; // OKだと思う。

f().x = 0 ; // でもこっちはエラー? なんか納得できない。

となると、f()の戻り値をそのまま変更することはできないけれど、rvalue referenceで束縛してしまえば変更できるという、へんな挙動になる。それはおかしいから、やはり#4はコンパイルエラーになるべきではないと思うのだが、#4を認める記述は、現在のドラフトには確認できない。うーん。教えてエロい人。

1 comment:

DigitalGhost said...

エロくないですけど…
Foo && ref = f();でref.xを変更できるのは、refが右辺値参照型の「左辺値」だからです。
一方で上のf().xが変更できてしまうと、右辺値を変更できることになってしまうので、そのへんの整合性をとるためにそういう仕様になってるんだと思います。
http://d.hatena.ne.jp/Cryolite/20080220#p1