Delete / Löschen
Adam Badura
06.10.2010 - 14:03

const reference / rvalue reference overloads

{ Please replace tabs with spaces before posting. TIA., -mod/aps }

Doesn't extensive use of rvalue references cause lots of overloads of
functions (in particular creation functions and constructors)? It
seems so to me. Lets consider this example:

class Person
{
public:
Person(
const std::string& first_name,
const std::string& last_name
)
: m_first_name( first_name ),
m_last_name( last_name )
{
}

const std::string& get_first_name() const
{
return m_first_name;
}

const std::string& get_last_name() const
{
return m_last_name;
}

private:
const std::string& m_first_name;
const std::string& m_last_name;

};

A very simple class. Correctly takes first_name and last_name as const
reference to avoid copying. (Although we might have used simple values
and swapping - however this requires a good swap function which cannot
be assumed in general.)

But we have now rvalue references which are even better so why not to
use them by adding additional constructor:

Person(
std::string&& first_name,
std::string&& last_name
)
: m_first_name( std::move( first_name ) ),
m_last_name( std::move( last_name ) )
{
}


All seems fine. We have a cool state-of-the-art code and so on. But
our eyes open in the moment we write following code:

void f( const Person& someone )
{
Person temp(
get_random_rvalue_first_name(),
someone.get_last_name()
);
}

This code no longer works as good. First argument is an rvalue
reference while second is an ordinary const reference. This results in
a call to the const reference constructor and no gain from moving the
rvalue. So it becomes clear we have to add yet another two constructor
overloads to cover all 4 cases. But it would be 8 and 16 with 3 and 4
arguments. It is obviously not acceptable.

So either Person will keep using old const references or it will have
to provide lots of overloads to cover all cases. It is not a good
situation.


How can be this overcome? It is possible to deal with it somehow? What
is needed is a way to say somehow "get whatever suits best and pass it
further".


The last sentence recalls forwarding. And in fact perfect forwarding
was the only thing I thought of to deal with this problem. Obviously
making Person constructor a template is not acceptable since it would
force us to deal with cumbersome error messages when incorrect
arguments are provided (like std::wstring which is easy with templates
as usually it is not clear what are the allowed types) and even more
importantly it would force us to keep implementation in header which
is often very undesired (due to dependency propagation and compilation
times).

It seems there is yet another trick. We could make a private function
to take only rvalue references. Such function can be placed in source
file normally as it is not template. And then provide a public
template forwarding function which would use some auxiliary template
magic to detect which arguments are passed by rvalue reference and
simple pass them further and which are passed by const reference and
construct a local object for them passing it further as rvalue
reference.


This seems to solve the problem. However to be usable with
constructors (like in this case) it requires ability to call a
different constructor overload from a constructor. (But this is to be
added to the language, isn't it?) Or to hide constructors and use
create functions.

Also another disadvantage of this solution is that the template
forwarding function would be rather complex (due to the auxiliary
templates to deal with arguments). Which enlarges not only the code
(to be read and understood) but also compilation time.

So are there any other tricks?

--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]


Adam Badura
06.10.2010 - 22:07
class Person
{
public:
Person(
const std::string& first_name,
const std::string& last_name
)
: m_first_name( first_name ),
m_last_name( last_name )
{
}

const std::string& get_first_name() const
{
return m_first_name;
}

const std::string& get_last_name() const
{
return m_last_name;
}

private:
const std::string& m_first_name;
const std::string& m_last_name;

};

The member variables obviously were meant to be std::string values
rather than const std::string& ones.

--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]


Nick Hounsome
06.10.2010 - 22:10
On 6 Oct, 13:03, Adam Badura <abad...@o2.pl> wrote:
{ Please replace tabs with spaces before posting. TIA., -mod/aps }

Doesn't extensive use of rvalue references cause lots of overloads of
functions (in particular creation functions and constructors)? It
seems so to me. Lets consider this example:

class Person
{
public:
Person(
const std::string& first_name,
const std::string& last_name
)
: m_first_name( first_name ),
m_last_name( last_name )
{
}

const std::string& get_first_name() const
{
return m_first_name;
}

const std::string& get_last_name() const
{
return m_last_name;
}

private:
const std::string& m_first_name;
const std::string& m_last_name;

};

A very simple class. Correctly takes first_name and last_name as const
reference to avoid copying. (Although we might have used simple values
and swapping - however this requires a good swap function which cannot
be assumed in general.)

No. this is terrible!

Person me("Nick","H");
cout << me.get_first_name(); // If you are unlucky this might work.


--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]



"Bo Persson"
06.10.2010 - 22:35
Adam Badura wrote:
Doesn't extensive use of rvalue references cause lots of overloads
of functions (in particular creation functions and constructors)? It
seems so to me. Lets consider this example:

class Person
{
public:
Person(
const std::string& first_name,
const std::string& last_name
)
m_first_name( first_name ),
m_last_name( last_name )
{
}

const std::string& get_first_name() const
{
return m_first_name;
}

const std::string& get_last_name() const
{
return m_last_name;
}

private:
const std::string& m_first_name;
const std::string& m_last_name;

};

A very simple class. Correctly takes first_name and last_name as
const reference to avoid copying. (Although we might have used
simple values and swapping - however this requires a good swap
function which cannot be assumed in general.)

But we have now rvalue references which are even better so why not
to use them by adding additional constructor:

Person(
std::string&& first_name,
std::string&& last_name
)
m_first_name( std::move( first_name ) ),
m_last_name( std::move( last_name ) )
{
}


All seems fine. We have a cool state-of-the-art code and so on. But
our eyes open in the moment we write following code:

void f( const Person& someone )
{
Person temp(
get_random_rvalue_first_name(),
someone.get_last_name()
);
}

This code no longer works as good. First argument is an rvalue
reference while second is an ordinary const reference. This results
in a call to the const reference constructor and no gain from
moving the rvalue. So it becomes clear we have to add yet another
two constructor overloads to cover all 4 cases. But it would be 8
and 16 with 3 and 4 arguments. It is obviously not acceptable.

So either Person will keep using old const references or it will
have to provide lots of overloads to cover all cases. It is not a
good situation.


How can be this overcome? It is possible to deal with it somehow?
What is needed is a way to say somehow "get whatever suits best and
pass it further".


It seems like the solution is to pass the parameters by value, and let the move
constructors take care of this!

http://cpp-next.com/archive/2009/09/move-it-with-rvalue-references/

Person(
std::string first_name,
std::string last_name
)
: m_first_name( std::move( first_name ) ),
m_last_name( std::move( last_name ) )
{ }

If you pass an rvalue to first_name, the parameter will be move constructed
twice (which is still rather cheap, and possibly optimzed to a single move by
the compiler).

If you pass an lvalue to last_name, it will be copied once (which it would be
anyway) to the parameter and then moved to m_last_name. The move again possibly
optimized away by the compiler.



Bo Persson



--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]


Dragan Milenkovic
07.10.2010 - 19:54
On 10/06/2010 10:35 PM, Bo Persson wrote:
Adam Badura wrote:
Doesn't extensive use of rvalue references cause lots of overloads
of functions (in particular creation functions and constructors)? It
seems so to me. Lets consider this example:

Just for completeness, the OP has corrected himself on using values
instead of references as members.

It seems like the solution is to pass the parameters by value, and let the move
constructors take care of this!

http://cpp-next.com/archive/2009/09/move-it-with-rvalue-references/

Nice article, I learned something new, thanks.

However, I don't like the idea that the function declaration is tied
so much to the implementation. I'll explain...

Person(
std::string first_name,
std::string last_name
)
: m_first_name( std::move( first_name ) ),
m_last_name( std::move( last_name ) )
{ }

This will do the best possible job concerning move and copy. But...

What happens if you decide not to simply copy/move both strings
into the destination member variables, but to use them in
a different manner, for example:

MyUnicodeName m_first_name, m_last_name;

Person(std::string first_name, std::string last_name)
: m_first_name(first_name),
m_last_name(last_name) {}

This will make unnecessary copies if lvalues have been provided
as arguments, and would be much better having been declared as
Person(const std::string &, const std::string &).

Again, I believe that this approach makes interface be dictated
by the implementation, and I don't like it at all! While it is
true that we can change the interface and recompile without
any harm, I feel that this is not a natural way of things.

Any thoughts? Does this make sense, or is it just my body
trying to reject move semantics (rvalue references have been
poking my liver like forever)?

--
Dragan

[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]



Luc Danton
07.10.2010 - 19:55
On 06/10/2010 14:03, Adam Badura wrote:
{ Please replace tabs with spaces before posting. TIA., -mod/aps }

Doesn't extensive use of rvalue references cause lots of overloads of
functions (in particular creation functions and constructors)? It
seems so to me. Lets consider this example:

class Person
{
public:
   Person(
      const std::string& first_name,
      const std::string& last_name
   )
      : m_first_name( first_name ),
       m_last_name( last_name )
   {
   }

   const std::string& get_first_name() const
   {
      return m_first_name;
   }

   const std::string& get_last_name() const
   {
      return m_last_name;
   }

private:
   const std::string& m_first_name;
   const std::string& m_last_name;

};

A very simple class. Correctly takes first_name and last_name as const
reference to avoid copying. (Although we might have used simple values
and swapping - however this requires a good swap function which cannot
be assumed in general.)

But we have now rvalue references which are even better so why not to
use them by adding additional constructor:

Person(
   std::string&& first_name,
   std::string&& last_name
)
   : m_first_name( std::move( first_name ) ),
    m_last_name( std::move( last_name ) )
{
}


All seems fine. We have a cool state-of-the-art code and so on. But
our eyes open in the moment we write following code:

void f( const Person& someone )
{
   Person temp(
      get_random_rvalue_first_name(),
      someone.get_last_name()
   );
}

This code no longer works as good. First argument is an rvalue
reference while second is an ordinary const reference. This results in
a call to the const reference constructor and no gain from moving the
rvalue. So it becomes clear we have to add yet another two constructor
overloads to cover all 4 cases. But it would be 8 and 16 with 3 and 4
arguments. It is obviously not acceptable.

So either Person will keep using old const references or it will have
to provide lots of overloads to cover all cases. It is not a good
situation.


How can be this overcome? It is possible to deal with it somehow? What
is needed is a way to say somehow "get whatever suits best and pass it
further".


The last sentence recalls forwarding. And in fact perfect forwarding
was the only thing I thought of to deal with this problem. Obviously
making Person constructor a template is not acceptable since it would
force us to deal with cumbersome error messages when incorrect
arguments are provided (like std::wstring which is easy with templates
as usually it is not clear what are the allowed types) and even more
importantly it would force us to keep implementation in header which
is often very undesired (due to dependency propagation and compilation
times).

It seems there is yet another trick. We could make a private function
to take only rvalue references. Such function can be placed in source
file normally as it is not template. And then provide a public
template forwarding function which would use some auxiliary template
magic to detect which arguments are passed by rvalue reference and
simple pass them further and which are passed by const reference and
construct a local object for them passing it further as rvalue
reference.


This seems to solve the problem. However to be usable with
constructors (like in this case) it requires ability to call a
different constructor overload from a constructor. (But this is to be
added to the language, isn't it?) Or to hide constructors and use
create functions.

Also another disadvantage of this solution is that the template
forwarding function would be rather complex (due to the auxiliary
templates to deal with arguments). Which enlarges not only the code
(to be read and understood) but also compilation time.

So are there any other tricks?


#include <type_traits>

class Person {
public:
template<typename T, typename U>
Person(T&& t, U&& u)
: m_first_name( std::forward<T>(t) )
, m_last_name( std::forward<U>(u) )
{
static_assert( std::is_convertible<T, std::string>::value
&& std::is_convertible<U, std::string>::value,
"Cannot initialize std::string from passed argument");
}
/* as before */
};

To be honest I'm not sure I like that "solution". In most cases I provide a
by-const-lref valued constructor + a by-rref overload if I feel like it. If
anyone will need to construct from both a rvalue and const lvalue there are ways
around it, like passing a temporary copy (std::string(some_lvalue)). Burdening
the user isn't really nice, but neither are too many overloads.

However, when thinking about it, Bo Persson's suggestion makes a lot of sense.
Best of both worlds really: no overloads, can construct from anything,
definition can be in another TU.

--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]


Luc Danton
08.10.2010 - 15:48
On 07/10/2010 19:54, Dragan Milenkovic wrote:
On 10/06/2010 10:35 PM, Bo Persson wrote:
Adam Badura wrote:
Doesn't extensive use of rvalue references cause lots of overloads
of functions (in particular creation functions and constructors)? It
seems so to me. Lets consider this example:

Just for completeness, the OP has corrected himself on using values
instead of references as members.

It seems like the solution is to pass the parameters by value, and let
the move
constructors take care of this!

http://cpp-next.com/archive/2009/09/move-it-with-rvalue-references/

Nice article, I learned something new, thanks.

However, I don't like the idea that the function declaration is tied
so much to the implementation. I'll explain...

Person(
std::string first_name,
std::string last_name
)
: m_first_name( std::move( first_name ) ),
m_last_name( std::move( last_name ) )
{ }

This will do the best possible job concerning move and copy. But...

What happens if you decide not to simply copy/move both strings
into the destination member variables, but to use them in
a different manner, for example:

MyUnicodeName m_first_name, m_last_name;

Person(std::string first_name, std::string last_name)
: m_first_name(first_name),
m_last_name(last_name) {}

This will make unnecessary copies if lvalues have been provided
as arguments, and would be much better having been declared as
Person(const std::string &, const std::string &).

Again, I believe that this approach makes interface be dictated
by the implementation, and I don't like it at all! While it is
true that we can change the interface and recompile without
any harm, I feel that this is not a natural way of things.

Any thoughts? Does this make sense, or is it just my body
trying to reject move semantics (rvalue references have been
poking my liver like forever)?


It depends on the constructors of MyUnicodeName. If the type has only
constructors taking std::string const&, then you shouldn't bother to
have your own overloads taking std::string&&. If MyUnicodeName has a
std::string&& overload (which, if you think about it, is unlikely unless
MyUnicodeName has an std::string member), you can either provide the two
usual overloads, or pass by value. The same goes if MyUnicodeName itself
has a by-value constructor.

All in all, since the types of the members are a part of the interface,
no additional coupling is introduced by using move mechanics for the
constructors, isn't it? Picking what constructors/what overload you will
provide depends on what members you will expose. If you change the
latter (which needs a recompilation) you'll need to change the former at
the same time.

Of course if you still can't stomach ralue refs you can just ignore them
altogether and still benefit from them.

--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]


Adam Badura
08.10.2010 - 23:48
However, when thinking about it, Bo Persson's suggestion makes a lot of sense.
Best of both worlds really: no overloads, can construct from anything,
definition can be in another TU.

How come? You just made me realize that Bo Persson solution (using
pure values rather than const/rvalue references) has the disadvantage
of requiring the definition of the class (std::string in my example)
at the point where the function (Person constructor in my example) is
declared.

While this is not a major issue in this case since std::string would
have to be known anyway (due to member variables) it might be an issue
if the arguments would later just passed to some other function.

--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]


Adam Badura
09.10.2010 - 01:40
Again, I believe that this approach makes interface be dictated
by the implementation, and I don't like it at all! While it is
true that we can change the interface and recompile without
any harm, I feel that this is not a natural way of things.

Any thoughts? Does this make sense, or is it just my body
trying to reject move semantics (rvalue references have been
poking my liver like forever)?

Good point. I always felt the same and now I feel it even more (as
there are more choices to be made).

--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]


Dragan Milenkovic
09.10.2010 - 22:03
On 10/08/2010 03:48 PM, Luc Danton wrote:
On 07/10/2010 19:54, Dragan Milenkovic wrote:
On 10/06/2010 10:35 PM, Bo Persson wrote:
Adam Badura wrote:
[snip]
However, I don't like the idea that the function declaration is tied
so much to the implementation. I'll explain...

Person(
std::string first_name,
std::string last_name
)
: m_first_name( std::move( first_name ) ),
m_last_name( std::move( last_name ) )
{ }

This will do the best possible job concerning move and copy. But...

What happens if you decide not to simply copy/move both strings
into the destination member variables, but to use them in
a different manner, for example:

MyUnicodeName m_first_name, m_last_name;

Person(std::string first_name, std::string last_name)
: m_first_name(first_name),
m_last_name(last_name) {}

This will make unnecessary copies if lvalues have been provided
as arguments, and would be much better having been declared as
Person(const std::string &, const std::string &).

Again, I believe that this approach makes interface be dictated
by the implementation, and I don't like it at all! While it is
true that we can change the interface and recompile without
any harm, I feel that this is not a natural way of things.

Any thoughts? Does this make sense, or is it just my body
trying to reject move semantics (rvalue references have been
poking my liver like forever)?


It depends on the constructors of MyUnicodeName. If the type has only
constructors taking std::string const&, then you shouldn't bother to
have your own overloads taking std::string&&. If MyUnicodeName has a
std::string&& overload (which, if you think about it, is unlikely unless
MyUnicodeName has an std::string member), you can either provide the two
usual overloads, or pass by value. The same goes if MyUnicodeName itself
has a by-value constructor.

But my argument is that it shouldn't matter. Representation
and implementation should be independent on the interface.
More argument below...

All in all, since the types of the members are a part of the interface,

This is only in the example given. In a general case, members are more
likely to be private, and about recompilation -- we can use pimple.
Also, "modules" keep being mentioned as a future C++ feature,
and I guess "they" won't like this, too (but I was just being
argumentative, no need to discuss modules now).

How about in this case:
virtual void set_name(??? first_name, ??? last_name) = 0;

[snip]
Of course if you still can't stomach ralue refs you can just ignore them
altogether and still benefit from them.

I can't stomach some of their usage which gains approval from this
and sister groups. In the meantime, I have come up with a solution
to this problem that solves all my issues and actually looks clean
and natural to me. Maybe there is a "prior art" here, but I did
neither find nor search for it.

Note that this is just a proof of concept, and only provides
the wanted functionality, so it's incomplete (among others,
lacking definitions of move/copy semantics; maybe error checks;
const_cast also feels a bit ugly although it's correct).
Also, I'm not sure it's a complete solution or does it
have shortcomings. Oh, it's not meant for non-const lvalue
reference parameters.

If I may...

template <typename T>
class param_wrapper {
const T * ptr;
bool is_lvalue;
public:
param_wrapper(const T & t) : ptr(&t), is_lvalue(true) {}
param_wrapper(T && t) : ptr(&t), is_lvalue(true) {}

T move() const {
if (is_lvalue) return *ptr;
else return std::move(const_cast<T &>(*ptr));
}

T copy() const {
return *ptr;
}
};

Person(
param_wrapper<std::string> first_name,
param_wrapper<std::string> last_name
) : m_first_name(first_name.move()), m_last_name(last_name.move()) {}

... Person ctor will now both do the right thing (tested on GCC
and it had the same number of copies/moves as other solutions),
and it also look good to me. It also handles the "virtual"
case I mentioned above.

--
Dragan

[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]


Luc Danton
09.10.2010 - 22:06
On 08/10/2010 23:48, Adam Badura wrote:
However, when thinking about it, Bo Persson's suggestion makes a lot of sense.
Best of both worlds really: no overloads, can construct from anything,
definition can be in another TU.

How come? You just made me realize that Bo Persson solution (using
pure values rather than const/rvalue references) has the disadvantage
of requiring the definition of the class (std::string in my example)
at the point where the function (Person constructor in my example) is
declared.

I meant that in relation to my own suggestion, which is a template.

While this is not a major issue in this case since std::string would
have to be known anyway (due to member variables) it might be an issue
if the arguments would later just passed to some other function.

Actually only a forward declaration for the arguments is needed when declaring
function/members, no matter the argument passing style. So no additional
coupling when passing by value as opposed to by reference.

--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]


"Bo Persson"
09.10.2010 - 22:11
Dragan Milenkovic wrote:
On 10/06/2010 10:35 PM, Bo Persson wrote:

Person(
std::string first_name,
std::string last_name
)
: m_first_name( std::move( first_name ) ),
m_last_name( std::move( last_name ) )
{ }

This will do the best possible job concerning move and copy. But...

What happens if you decide not to simply copy/move both strings
into the destination member variables, but to use them in
a different manner, for example:

MyUnicodeName m_first_name, m_last_name;

Person(std::string first_name, std::string last_name)
: m_first_name(first_name),
m_last_name(last_name) {}

This will make unnecessary copies if lvalues have been provided
as arguments, and would be much better having been declared as
Person(const std::string &, const std::string &).

Again, I believe that this approach makes interface be dictated
by the implementation, and I don't like it at all! While it is
true that we can change the interface and recompile without
any harm, I feel that this is not a natural way of things.

Oh, I always belived the implementation was dictated by the interface. :-)

Any thoughts? Does this make sense, or is it just my body
trying to reject move semantics (rvalue references have been
poking my liver like forever)?

Your use of move semantics looks like a, possibly premature :-), attempt to
somewhat optimize the implementation. It doesn't change the interface of the
constructor taking two strings.

There are no requirements for you to define move semantics for your classes,
just an additional tool to use whenever it makes sense.


Bo Persson



--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]


Luc Danton
10.10.2010 - 22:54
On 09/10/2010 22:03, Dragan Milenkovic wrote:
On 10/08/2010 03:48 PM, Luc Danton wrote:
On 07/10/2010 19:54, Dragan Milenkovic wrote:
On 10/06/2010 10:35 PM, Bo Persson wrote:
Adam Badura wrote:
[snip]
However, I don't like the idea that the function declaration is tied
so much to the implementation. I'll explain...

Person(
std::string first_name,
std::string last_name
)
: m_first_name( std::move( first_name ) ),
m_last_name( std::move( last_name ) )
{ }

This will do the best possible job concerning move and copy. But...

What happens if you decide not to simply copy/move both strings
into the destination member variables, but to use them in
a different manner, for example:

MyUnicodeName m_first_name, m_last_name;

Person(std::string first_name, std::string last_name)
: m_first_name(first_name),
m_last_name(last_name) {}

This will make unnecessary copies if lvalues have been provided
as arguments, and would be much better having been declared as
Person(const std::string &, const std::string &).

Again, I believe that this approach makes interface be dictated
by the implementation, and I don't like it at all! While it is
true that we can change the interface and recompile without
any harm, I feel that this is not a natural way of things.

Any thoughts? Does this make sense, or is it just my body
trying to reject move semantics (rvalue references have been
poking my liver like forever)?


It depends on the constructors of MyUnicodeName. If the type has only
constructors taking std::string const&, then you shouldn't bother to
have your own overloads taking std::string&&. If MyUnicodeName has a
std::string&& overload (which, if you think about it, is unlikely unless
MyUnicodeName has an std::string member), you can either provide the two
usual overloads, or pass by value. The same goes if MyUnicodeName itself
has a by-value constructor.

But my argument is that it shouldn't matter. Representation
and implementation should be independent on the interface.
More argument below...

All in all, since the types of the members are a part of the interface,

This is only in the example given. In a general case, members are more
likely to be private, and about recompilation -- we can use pimple.
Also, "modules" keep being mentioned as a future C++ feature,
and I guess "they" won't like this, too (but I was just being
argumentative, no need to discuss modules now).

If you use pimpl then you have designed or are about to design a stable
interface. Pick one of the two possible overloads and be done with it. (And
ignore those cases where the user would want to pass both an lvalue and an
rvalue: more on this later.)

My original point about this is that if you're writing the class _now_, and you
just specified the members (so no pimpl), then writing the constructors should
be easy in general: accept what you want to pass to the member constructors.

How about in this case:
virtual void set_name(??? first_name, ??? last_name) = 0;

Since you're not going to overload a virtual function, pick one of the two:
regular style (possibly const) lvalue references, or rvalue references. But the
latter should be reserved when either the semantics are clear (some sort of push
or emplace function for instance, where it make sense to 'move' the argument(s)
to another context) or if the type is really expensive to copy.

In other words, rvalue refs should be considered an optimization or a special
tool. Just because they're here in the language (well, will be here) doesn't
mean you should try to use them at every opportunity. In a lot of cases, plain
copying is OK. (Bo Persson expressed the same idea elsethread.)

Also consider that if you provide only one overload:
* given:
virtual void set_name(std::string& first, std::string& last);
if the user has a temporary he will bind it before calling the function. Even if
it's not an std::string there's auto for complicated types.

* given:
virtual void set_name(std::string const& first, std::string const& last);
if the user has a temporary he can pass it already.

* given:
virtual void set_name(std::string&& first, std::string&& last);
the user can pass std::string(lval); where lval is a possibly const lvalue.

[snip]
Of course if you still can't stomach ralue refs you can just ignore them
altogether and still benefit from them.

I can't stomach some of their usage which gains approval from this
and sister groups. In the meantime, I have come up with a solution
to this problem that solves all my issues and actually looks clean
and natural to me. Maybe there is a "prior art" here, but I did
neither find nor search for it.

Note that this is just a proof of concept, and only provides
the wanted functionality, so it's incomplete (among others,
lacking definitions of move/copy semantics; maybe error checks;
const_cast also feels a bit ugly although it's correct).
Also, I'm not sure it's a complete solution or does it
have shortcomings. Oh, it's not meant for non-const lvalue
reference parameters.

If I may...

template <typename T>
class param_wrapper {
const T * ptr;
bool is_lvalue;
public:
param_wrapper(const T & t) : ptr(&t), is_lvalue(true) {}
param_wrapper(T && t) : ptr(&t), is_lvalue(true) {}

I assume there is a typo and you meant is_lvalue(false) here.

T move() const {
if (is_lvalue) return *ptr;
else return std::move(const_cast<T &>(*ptr));
}

T copy() const {
return *ptr;
}
};

Person(
param_wrapper<std::string> first_name,
param_wrapper<std::string> last_name
) : m_first_name(first_name.move()), m_last_name(last_name.move()) {}

... Person ctor will now both do the right thing (tested on GCC
and it had the same number of copies/moves as other solutions),
and it also look good to me. It also handles the "virtual"
case I mentioned above.


I looks like you're implementing some sort of perfect forwarding by hand. Have
you considered a perfect-forward template constructor like I suggested
elsethread? It would also handle non-const lvalues.

I agree that it your solution has advantages (less coupling) but this looks like
over-engineering for what I feel is a non-issue. Right now with C++03 you can't
even detect rvalues and suddenly you have to take care of them at every
opportunity? Why?

--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]


Dragan Milenkovic
11.10.2010 - 17:48
On 10/10/2010 10:54 PM, Luc Danton wrote:
[snip]
In other words, rvalue refs should be considered an optimization or a
special tool. Just because they're here in the language (well, will be
here) doesn't mean you should try to use them at every opportunity. In a
lot of cases, plain copying is OK. (Bo Persson expressed the same idea
elsethread.)

A few lines from
http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/

"Guideline: Don’t copy your function arguments. Instead,
pass them by value and let the compiler do the copying."

"""
It’s now “obvious” that the correct way to write
a copy-and-swap assignment is:

T& operator=(T x) // x is a copy of the source; [snip]
swap(*this, x); // trade our resources for x's
return *this; // our (old) resources get destroyed with x
}
"""

It just goes beyond constructors that initialize members
with arguments. The same thought comes to my mind:
what if we (for whatever reason) want to change the
implementation of the assignment operator? I know
that this should NOT happen, especially for
a copy-and-swap assignment! But it simply is a question
that is bothering me and it would help me to accept
new C++ technologies and not become a C++ dinosaur. :-D
Hope this explains my other concerns... I know how
I would do it just now, but I'm trying to learn more...

However, if we're having two overloads, only
the implementation would need to change.

Also, I don't like my over-engineered code
anymore, and the reason is that it would
make all functions that would potentially
benefit from move semantics have a wrapper
instead of a parameter. What was I thinking!?!

--
Dragan

[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]




Ähnliche Themen

C++ Tip : Binding a Reference to an Rvalue
29.06.2010 - 23:01 - Posts: 2

C++11 rvalue reference question
05.12.2011 - 06:38 - Posts: 6

C++0x, N2960, rvalue reference issues
12.11.2009 - 18:24 - Posts: 3

How is operator+ called with rvalue reference?
09.09.2011 - 04:45 - Posts: 2

Rvalue Reference argument binding
19.04.2010 - 22:59 - Posts: 1

N3090: Rvalue reference example in 8.5.3/5 correct or wrong?
17.06.2010 - 20:00 - Posts: 4

rvalue-reference result premature destruction
11.04.2010 - 00:34 - Posts: 2

More

Share/Bookmark

<