Re: const reference / rvalue reference overloads
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! ]