views:

102

answers:

3

What is the best way to bind an rvalue reference to either a given object or a temporary copy of it?

A &&var_or_dummy = modify? static_cast<A&&>( my_A )
                         : static_cast<A&&>( static_cast<A>( my_A ) );

(This code doesn't work on my recent GCC 4.6… I recall it working before, but now it always returns a copy.)

On the first line, the static_cast transforms my_A from an lvalue to an xvalue. (C++0x §5.2.9/1-3) The inner static_cast on the second line performs lvalue-to-rvalue conversion, and the outer one obtains an xvalue from this prvalue.

This appears to be supported because the named reference is conditionally bound to the temporary per §12.2/5. The same trick works the same way in C++03 with a const reference.

I can also write the same thing less verbosely:

A &&var_or_dummy = modify? std::move( my_A )
                         : static_cast<A&&>( A( my_A ) );

Now it's much shorter. The first abbreviation is questionable: move is supposed to signal that something is happening to the object, not a mere lvalue-to-xvalue-to-lvalue shuffle. Confusingly, move cannot be used after the : because the function call would interrupt the temporary-to-reference binding. The syntax A(my_A) is perhaps clearer than the static_cast, but it's technically equivalent to a C-style cast.

I can also go all the way and write it entirely in C-style casts:

A &&var_or_dummy = modify? (A&&)( my_A ) : (A&&)( A( my_A ) );

After all, if this is going to be an idiom, it must be convenient, and static_cast isn't really protecting me from anything anyway — the real danger is failing to bind directly to my_A in the true case.

On the other hand, this easily gets dominated by the typename repeated three times. If A were replaced with a big, ugly template-id, I'd really want a real shortcut.

(Note that V is evaluated only once despite appearing five times:)

#define VAR_OR_DUMMY( C, V ) ( (C)? \
  static_cast< typename std::remove_reference< decltype(V) >::type && >( V ) \
: static_cast< typename std::remove_reference< decltype(V) >::type && >   (  \
  static_cast< typename std::remove_reference< decltype(V) >::type >( V ) ) )

Hackish as macros are, I think that's the best alternative of the bunch. It's a bit dangerous because it returns an xvalue, so it shouldn't be used outside reference initialization.

There must be something I haven't thought of… suggestions?

+2  A: 

I see two issues with your approach.

You rely on the behaviour

int   i = 0;
int&  j = true?      i  :      i;
int&& k = true? move(i) : move(i);
assert(&i == &j); // OK, Guaranteed since C++98
assert(&i == &k); // Does this hold as well?

The current standard draft N3126 contains 5.16/4:

If the second and third operands [to the conditional operator] are glvalues of the same value category and have the same type, the result is of that type and value category

which makes me think that the above two asserts should hold. But using GCC 4.5.1 the second one fails. I believe that this is a GCC bug.

In addition, you rely on the compiler to extend life time of the temporary object y refers to in the following example:

A func();

A&& x = func();                   // #1
A&& y = static_cast<A&&>(func()); // #2

x won't be a dangling reference but I'm not so sure about y. I think the rule about extending the life-time of temporaries is only supposed to apply in cases the initializer expressions are pure rvalues. At least, this would greatly simplify the implementation. Also, GCC seems to agree with me on this one. GCC doesn't extend the life-time of the temporary A object in the second case. This would be a dangling-reference problem in your approach.

Update: According to 12.2/5 the lifetimes of the temporary objects are supposed to be extended in both cases, #1 and #2. None of the bullet points in the list of exceptions seem to apply here. Again, GCC seems to be buggy in this regard.

One easy solution for your problem would be:

vector<A> tempcopy;
if (!modify) tempcopy.push_back(myA);
A& ref = modify ? myA : tempcopy.back();

Alternativly, you could use a boost::scoped_ptr instead of a vector.

sellibitze
Yep… (In your first example, `static_cast` is necessary instead of `move` which is a function.) The FCD doesn't differentiate between lvalue references and rvalue references in the lifetime extension clause, so I'm pretty sure all references are alike in that respect. +1 for the workaround… too bad C++0x didn't import Boost Optional, which is the best tool for that job.
Potatoswatter
@Potatoswatter: I'm confused. Why do you think that it makes any difference in the *first* example whether move or static_cast is used? The first example isn't about life-time issues. It's about whether the resulting xvalue expression actually refers to the original object.
sellibitze
@sellibitze: 12.2/5. One key idea is that the intermediate expressions are xvalues, not references, so the phrasing "reference is bound" exclusively refers to named objects. (And the return value of a function declared with reference type, which is an excluded case.)
Potatoswatter
@Potatoswatter: As for the "lifetime extension clause", please cite the relevant parts of the current draft. I actually tried to look that up but -- as I said -- I didn't find anything conclusive.
sellibitze
lifetime extension clause 12.2/5 includes "[temporary] persists for the lifetime of a reference except… A temporary bound to a reference parameter in a function call (5.2.2) persists until the completion of the full-expression containing the call." — never mind what I said about your example, it's not using temporaries anyway.
Potatoswatter
@Potatoswatter: Thanks, that has cleared up my doubts.
sellibitze
+2  A: 

Just avoid this whole mess with an extra function call:

void f(bool modify, A &obj) {
  return [&](A &&obj) {
    real();
    work();
  }(modify ? std::move(obj) : std::move(A(obj)));
}

Instead of:

void f(bool modify, A &obj) {
  A &&var_or_dummy = /* ??? */;
  real();
  work();
}

It's lambdas, lambdas, everywhere!

Roger Pate
Are you sure that the result of the conditional operator can ever refer to the original object in this case? The second operand is an xvalue and the third operand is a prvalue. And even if you add a std::move() around A(obj), this won't work with GCC as GCC seems to be buggy w.r.t. the conditional operator and xvalue operands. I don't have the most recent GCC build. Did you test this code by any chance?
sellibitze
@sellibitze: This is the canonical way to use xvalues. The same bug with `?:` and xvalues affects my code… I'd expect that to get fixed before long. (And it did work in the past.)
Potatoswatter
+1, this is also the canonical way to use lambda expressions. This would tend to test C++0x's functional purity… how much difference does it *really* make that this block has its own stack frame?
Potatoswatter
@Potatoswatter: I'm not convinced that this is supposed to work. From what I can tell paragraph 5 of 5.16 seems to apply in this case which makes the conditional operator expression yield a prvalue.
sellibitze
@Roger: on the look-alike principle, it might be best to capture default by reference…
Potatoswatter
@sellibitze: Yes, you're right, `move(A(obj))` is necessary… trivial change.
Potatoswatter
I think this *should* work according to the current standard draft, but it *doesn't* on GCC because the ternary operator yields a prvalue in this case instead of an xvalue that refers to obj or the temporary.
sellibitze
A: 

The issue of xvalue safety can be worked around somewhat by providing an alternative for use inside expressions. The issues are completely different, now we don't want an xvalue result and can use a function:

template< typename T >
T &var_or_dummy( bool modify, T &var, T &&dummy = T() ) {
    if ( modify ) return var;
    else return dummy = var;
}

    maybe_get_result( arg, var_or_dummy( want_it, var ) );

Now the type has to be default-constructible, and the dummy is always constructed. The copy is conditionally evaluated. I don't think I'd really want to deal with code that did too much of this.

Boost Optional can help a bit; it only requires CopyConstructible T:

template< typename T >
T &var_or_dummy( bool modify, T &var,
                 boost::optional< T > &&dummy = boost::optional< T >() ) {
    if ( modify ) return var;
    else return dummy = var;
}

Optional is useful, but it has some overlap with C++0x unions. It's not too hard to reimplement.

template< class T >
struct optional_union {
    bool valid;
    union storage {
        T obj; // union of one non-POD member simply reserves storage

        storage() {} // uh, what could the constructor/destructor possibly do??
        ~storage() {}
    } s;

    optional_union() : valid( false ) {}
    optional_union &operator=( T const &in ) {
        new( &s.obj ) T( in ); // precondition: ! valid
        valid = true;
        return *this; 
    }
    ~optional_union()
        { if ( valid ) s.obj.~T(); }
};

template< typename T >
T &var_or_dummy( bool modify, T &var,
                 optional_union< T > &&dummy = optional_union< T >() ) {
    if ( modify ) return var;
    else return ( dummy = var ).s.obj;
}

The optional_union class is only sufficient for this application… obviously it could be expanded a lot.

Potatoswatter
Since you already put "the work" into a separate function for your first example, you could just as well write `if (modify) { work(myA); } else { A copy = myA; work(copy); }` ;-)
sellibitze
@sellibitze: The object is to avoid code duplication.
Potatoswatter