It seems to me that in C++11 lots of attention was made to simplify returning va
ID: 653155 • Letter: I
Question
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?
Explanation / Answer
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.
Related Questions
drjack9650@gmail.com
Navigate
Integrity-first tutoring: explanations and feedback only — we do not complete graded work. Learn more.