It seems to me that in C++11 lots of attention was made to simplify returning values from functions and methods, i.e.: with move semantics it’s possible to simply return heavy-to-copy but cheap-to-move values (while in C++98/03 the general guideline was to use output parameters via non-const references or pointers), e.g.:
// C++11 style
vector<string> MakeAVeryBigStringList();
// C++98/03 style
void MakeAVeryBigStringList(vector<string>& result);
On the other side, it seems to me that more work should be done on input parameter passing, in particular when a copy of an input parameter is needed, e.g. in constructors and setters.
My understanding is that the best technique in this case is to use templates and std::forward<>
, e.g. (following the pattern of this answer on C++11 optimal parameter passing):
class Person
{
std::string m_name;
public:
template <class T,
class = typename std::enable_if
<
std::is_constructible<std::string, T>::value
>::type>
explicit Person(T&& name)
: m_name(std::forward<T>(name))
{
}
...
};
A similar code could be written for setters.
Frankly, this code seems boilerplate and complex, and doesn’t scale up well when there are more parameters (e.g. if a surname attribute is added to the above class).
Would it be possible to add a new feature to C++11 to simplify code like this (just like lambdas simplify C++98/03 code with functors in several cases)?
I was thinking of a syntax with some special character, like @
(since introducing a &&&
in addition to &&
would be too much typing 🙂
e.g.:
class Person
{
std::string m_name;
public:
/*
Simplified syntax to produce boilerplate code like this:
template <class T,
class = typename std::enable_if
<
std::is_constructible<std::string, T>::value
>::type>
*/
explicit Person(std::string@ name)
: m_name(name) // implicit std::forward as well
{
}
...
};
This would be very convenient also for more complex cases involving more parameters, e.g.
Person(std::string@ name, std::string@ surname)
: m_name(name),
m_surname(surname)
{
}
Would it be possible to add a simplified convenient syntax like this in C++?
What would be the downsides of such a syntax?
3
Sadly, no, because there’s too many cases. In your sample, you use std::string@
to represent the perfectly forwarded type of an object that should be perfectly forwarded to a std::string
constructor, and say “A similar code could be written for setters.”. But you’re wrong. You’d need another seperate syntax for assignment. For instance, I can construct a std::vector<anything>
from an int
, but I can’t assign an int
to a std::vector<anything>
. So I’d need like std::vector<anything>#
for assignments. And what about the +
operator? If I want to perfect forward a RHS to a member’s operator+
, then I’d need a notation for that too. And it can’t be an existing symbol like +
or that would make C++ much harder to parse than it already is! So you can see that this doens’t apply universally how you appear to think it does.
Secondly, I disagree that the existing boilerplate doesn’t scale well. It scales linearly, which is pretty well I think. (Note that the members and the mem-init-list boilerplate is required in any case and is thus not part of the scaling. Even if it were, that’s still linear)
class Person
{
std::string m_name;
std::string m_address;
std::string m_nickname;
std::string m_phonenumber;
std::string m_comment;
public:
template <class T, class U, class V, class W, class X,
class = typename std::enable_if <
std::is_constructible<std::string, T>::value &&
std::is_constructible<std::string, U>::value &&
std::is_constructible<std::string, V>::value &&
std::is_constructible<std::string, W>::value &&
std::is_constructible<std::string, X>::value
>::type>
explicit Person(T&& name, U&& addr, V&& nick, W&& phone, X&& comment)
: m_name(std::forward<T>(name)),
m_address(std::forward<T>(addr)),
m_nickname(std::forward<T>(nick)),
m_phonenumber(std::forward<T>(phone)),
m_comment(std::forward<T>(comment)),
{
}
...
};
Third: This is only needed when you need to pass an unknown type perfectly to the member, which is very rare. Normally, you’d just take all the members as std::string
by value, and move them into the members, which is amazingly close to optimal considering how amazingly easy it is.
8
Frankly, this code seems boilerplate and complex
Probably because it’s completely wrong. The “By value” part of that answer was what you were supposed to do. The other part, nobody ever does that. The only reason to do so was that you knew in advance your data member was non-movable (rather rare) and non-copyable and you didn’t know what it could be constructed from, an extremely rare combination of circumstances. It doesn’t even cover that circumstance, since it would have to be variadic.
class Person {
std::string name;
public:
Person(std::string cname)
: name(std::move(cname)) {}
};
Done.
4