C++11 Parameter Passing – Simplifying Optimal Parameter Passing When a Copy is Needed

cc++11language-design

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?

Best 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.