Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > comp.std.c++ > #355

Re: Any better way for std::move and std::forward usage ?

From Daniel Krügler <daniel.kruegler@googlemail.com>
Newsgroups comp.std.c++
Subject Re: Any better way for std::move and std::forward usage ?
Date 2011-11-02 11:20 -0700
Organization A noiseless patient Spider
Message-ID <j8pj78$3bj$1@dont-email.me> (permalink)
References <73a83b22-6c14-4700-81f3-0a99c8d727fc@u13g2000prm.googlegroups.com>

Show all headers | View raw


Am 01.11.2011 19:15, schrieb ujjwal:
> I am trying to understand move symantics in c++0x. I have a small
> program which illustrates the cases when move constructor should be
> called.

Let me begin with the remark, that for understanding move semantics and
"perfect forwarding" functions you really need to understand the
concepts of value categories in C++. For the level needed here, it is
sufficient to distinguish the classical two value categories, lvalues
and rvalues, where the former refer to some nameable object (or
function) with a memory identity and the latter refer to an (unnamed)
temporary object or non-object value (like a literal such as 'true' or
'42').

> I am looking for better ways to eliminate code duplication -
>
> e.g. I have assign function which reassigns the member variables.
> (Instead of a separate function I could have used deligated
> constructors but my VC version does not support yet.)
>
> I use this function in copy, move constructors and assignment
> operators.
>
> template<typename T1, typename T2, typename T3>
>    void assign(T1&&   id, T2&&   name, T3&&   address)
> 	{
> 		_id = std::forward<T1>(id);
> 		_name = std::forward<T2>(name);
> 		_address = std::forward<T3>(address);
> 	}
>
> Q : Why do I need to call std::move explicitely ?

I do not see any std::move here and you don't need it: You are defining
here a "perfect forwarding" signature here. This means that the actually
deduced type for any argument reflects the value category of this
argument. Any lvalue will deduce to some lvalue-reference type Ti
(i=1,2,3), any rvalue will deduce to a non-reference type Ti. If you
would directly refer to the names id, name, or address, these are always
lvalues (and you would loose the information contained within the
deduced types), because they are "named" variables. To conserve the
deduced value, category, std::forward is used here. It has the effect of
std::move, if the value was an rvalue, else it is just an "identity"
transfer.

> Why e._id does not
> resolve to&&   as e is passed by&&
>
> Employee(Employee&&   e)
> 	{
> 		std::cout<<   "Employee::Employee Move"<<   std::endl;
> 		/**
> 		 * This version VC10 does not support delegating constructors.
> 		 */
> 		assign(std::move(e._id), std::move(e._name),
> std::move(e._address));
> 	}

Your original presentation let me read you meant your assign function
above, but the latter text makes only sense, if you intended to refer to
this constructor. But we can borrow one conclusion from above: Since e
is a named variable, this is an lvalue. Note that types or type
categories (like lvalue-reference or rvalue-reference) are generally
orthogonal to value categories. Thus by recognicing an rvalue-reference,
this doesn't automatically mean that the value category of this thing is
an rvalue. First, all function rvalue references are lvalues, second,
any named rvalue reference is an lvalue. Don't try to mix type category
and value category, that doesn't work!
So, to ensure that the lvalues e._id, e._name, and e._address are
properly *propagated* as rvalues to the assign template, you need
std::move here, because the expression std::move(obj) is an rvalue for
any expression obj that is an object type.

> class Employee
> {
> public:

[..]

> 	template<typename T1, typename T2, typename T3>
> 	void assign(T1&&   id, T2&&   name, T3&&   address)
> 	{
> 		_id = std::forward<T1>(id);
> 		_name = std::forward<T2>(name);
> 		_address = std::forward<T3>(address);
> 	}
>
> 	template<typename T1, typename T2, typename T3>
> 	Employee(T1&&   id, T2&&   name, T3&&   address)
> 	{
> 		assign(id, name, address);
> 	}

Here you need std::forward again. As written, every parameter - whether
it was *originally* an rvalue or an lvalue - will be transferred as
lvalue. Let me warn you, that replacing copy/move construction by
assignment does not work in all cases and may also perform unnecessary
additional work. In this example std::string and the Address member will
be default-initialized. I suggest to keep the original form.

> 	Employee(Employee&&   e)
> 	{
> 		std::cout<<   "Employee::Employee Move"<<   std::endl;
> 		assign(std::move(e._id), std::move(e._name),
> std::move(e._address));  ///@todo - Why do I need to call std::move
> explicitely ? Why e._id does not resolve to&&   as e is passed by&&
> 	}

As explained above, the template deduction depends on the value category
and the type. e as a named variable is an lvalue, so are it's
subobjects. You need std::move to transform back to an rvalue again.
Actually this is similar to why you need std::forward in the
aforementioned constructor template.

If you want to get rid of unnecessary code, you could simple
user-default this member as

Employee(Employee&&) = default;

> 	Employee&   operator = (Employee&&   e)
> 	{
> 		std::cout<<   "Employee::Operator = Move"<<   std::endl;
> 		assign(std::move(e._id), std::move(e._name),
> std::move(e._address)); ///@todo - Why do I need to call std::move
> explicitely ? Why e._id does not resolve to&&   as e is passed by&&
> 		return *this;
> 	}

Same reason as above. To simplify, just user-default this member like so:

Employee& operator=(Employee&&) = default;

> 	Employee&   operator = (const Employee&   e)
> 	{
> 		std::cout<<   "Employee::Operator =  Copy"<<   std::endl;
> 		assign(e._id, e._name, e._address);
> 		return *this;
> 	}
>
> 	///@note instead of repeating the code for all 4 types of functions
> which do similar work, is there any smart idea like std::forward until
> following is available ?
> 	/**
> 	 * Employee&   operator = (Employee&&   e) = default;
> 	 * Employee(Employee&&   e) = default
> 	 * section 8.4.2 c++11 iso standards
> 	 */

User-defaulting these members looks like the most reasonble approach:

Employee(const Employee&) = default;
Employee& operator=(const Employee&) = default;

HTH & Greetings from Bremen,

Daniel Krügler


-- 
[ comp.std.c++ is moderated.  To submit articles, try posting with your ]
[ newsreader.  If that fails, use mailto:std-cpp-submit@vandevoorde.com ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]

Back to comp.std.c++ | Previous | NextPrevious in thread | Next in thread | Find similar


Thread

Any better way for std::move and std::forward usage ? ujjwal<ujjwal.rp@gmail.com> - 2011-11-01 11:15 -0700
  Re: Any better way for std::move and std::forward usage ? Howard Hinnant<howard.hinnant@gmail.com> - 2011-11-02 11:15 -0700
  Re: Any better way for std::move and std::forward usage ? "Kevin P. Fleming"<news@kpfleming.us> - 2011-11-02 11:15 -0700
  Re: Any better way for std::move and std::forward usage ? SG <s.gesemann@gmail.com> - 2011-11-02 11:17 -0700
  Re: Any better way for std::move and std::forward usage ? Daniel Krügler <daniel.kruegler@googlemail.com> - 2011-11-02 11:20 -0700
    Re: Any better way for std::move and std::forward usage ? ujjwal <ujjwal.rp@gmail.com> - 2011-11-03 17:09 -0700

csiph-web