Boost logo

Boost :

Subject: Re: [boost] rvalue ref best practices?
From: Ion Gaztañaga (igaztanaga_at_[hidden])
Date: 2012-06-10 16:38:45

El 09/06/2012 22:49, Giovanni Piero Deretta escribió:
> On Sat, Jun 9, 2012 at 9:21 PM, Daniel Larimer<dlarimer_at_[hidden]>
> wrote:
>> I am trying to define my library API and have been using rvalue
>> references and have observed some patterns that I would like to run
>> by the community.
>> In c++03 I would pass almost everything by const& unless it was to
>> be modified. This results in extra copies any time a temporary is
>> passed.

> As per
> , if the function is likely to consume or copy the parameter, the
> best solution is to pass by value. This means you'll only need to
> provide a single overload.

What do you mean by "consume"? Passing by value is good if you are going
to construct a new object in your implementation. If you want to reuse
resources of the passed object (memory from std::string, already
constructed objects from a container...) passing by reference is the
best choice. If you want to reuse resources from "*this", passing a
const reference is the best way: implementing vector::operator=() with
pass by value would be a terrible idea IMHO. You can catch temporaries
by rvalue references to reuse external resources. For generic code,
catching by value could do some premature pessimization.

Passing by value has also another downside: it makes your binary
interface dependent on the function implementation (do I copy the
parameter?). If I change the implementation I need to break ABI.

I'm really worried about "pass by value is free" and "return by value is
free" statements I'm hearing in some C++11 panels.


A bit off-topic: language extension to achieve maximum efficiency

When thinking in solutions to this pass by value/reference issues, I
feel C++ is missing some language feature so that a function can detect
(at runtime, avoiding instantiations) if the output parameter is already
constructed. Since function signature must unique, the caller should
pass that information to the function in runtime.

The syntax to write generic code with N (possibly constructed)
parameters is not an easy task. For output parameters (I haven't come
with a good syntax for input parameters), we could expand NRVO experience:

We can pass the state of the output parameter in the lower bit of the
address (if objects are aligned, although it might interfere with common
optimizations like encoding rbtree color in the lower bit of the parent
pointer) or a hidden additional parameters using a bitset (a bitset
might hold 32/64 states and it's calculated at compile time in the
caller side). This syntax also allows multiple return types. Say:

void function
   ( A &a default(1) //return parameters or output references
   , B &b default{2, 9}
   , C &c default(b,c)
   , int a //normal parameter
   ) multireturn //or something that tells the compiler
                 //"this function takes implicit bitset"
                 //that indicates if "default" constructors
                 //must be called. Maybe it could be
                 //detected from "default".
    //a, b, & c are in indeterminate state (they might be
    //copies of already constructed output parameters or
    //constructed with default syntax so that RVO is applied.

    //a, b & c might reuse internal memory
    a = ...
    b = ...
    c = ...


//RVO is used for all parameters
//default initialization syntax used
//inside "function"
[A a; B b; C c] = function(a, b, c, 0);

A a;
a = ...

//RVO for b & c, output reference
//for a
[B b; C c] = function(a, b, c, 0);

//RVO is used for a copy elision for b
//default initialization syntax used
//inside "function"
[A a; B b; C c] = function(a, b, c, 0);

I think this might have issues with exception handling, but I think RVO
already has these problems as the copy elision might avoid a copy
exception when returning an object. Function overload might be also very
complicated (although we could consider return types as non-const
references). Function pointers and pointer to members could also be
affected (an extra hidden parameter is needed).

For input parameters things are more complicated. Say:

void function
   ( mutable const A &a ) //const input parameter or copy

Depending on a runtime value, we can modify the source (a rvalue was
catched as const reference) or not (a lvalue was catched). Think about
it as a runtime alternative to the templated perfect forwarding (T &&t)

void function
   (mutable const A &a ) //const input parameter or copy
   //A rvalue was catched, we can modify 'a'
   //Passed by const-reference 'a' is immutable
   else {
      this->a_ = a;

To achieve some basic safety, we need help from the compiler: 'a 'can't
be used as non-const if code is not placed inside a mutable scope:

void function
   (mutable const A &a ) //const input parameter or copy
{ //Ok, read operations and const member functions
    //can be called anywhere
    b = a
    n = a.size()

if(mutable(a, x...)){
   //Ok, a is modified only when it's mutable
   //Compilation error: non-const member function.
   //Compilation error: used as non-const reference.

Boost list run by bdawes at, gregod at, cpdaniel at, john at