4

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
  • 1
    Since you're using @ to represent perfect forwarding anyway, I'm pretty sure the std::forward<T> is redunant. (Especially since there's no T declared.) Oct 30, 2012 at 19:16
  • @MooingDuck: You're right.
    – Mr.C64
    Oct 30, 2012 at 19:19
  • @MooningDuck: I've updated the code with the "hypothetical" simplified syntax according to that.
    – Mr.C64
    Oct 30, 2012 at 19:32

2 Answers 2

2

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
  • @MooningDuck: my understanding is that pass by value + move from the value is suboptimal with respect to the perfect forwarding solution.
    – Mr.C64
    Oct 30, 2012 at 18:55
  • @Mr.C64: Yes, it can be suboptimal in some situations sometimes. Seriously, do it anyway. When the optimizer doesn't optimize it properly (which is rare), it is suboptimal by three pointer copies. Also, it's better code, which is far more important than a few measely pointer copies. Simpler Code = fewer mistakes = faster code. Oct 30, 2012 at 18:58
  • @MooningDuck: For a setter, would it be wrong for code like this void SetName(std::string@ name) { m_name = std::forward<T>(name); } to be expanded to template <class T, class = typename std::enable_if<std::is_constructible<std::string, T>::value>::type> void SetName(T&& name) { m_name = std::forward<T>(name);}?
    – Mr.C64
    Oct 30, 2012 at 19:08
  • @MooningDuck: I agree with you that simpler code == fewer mistakes, that's why I was having that idea of syntax simplification (but I was not sure, and decided to ask others for insights - exchanging feedback in a polite way is a good thing).
    – Mr.C64
    Oct 30, 2012 at 19:09
  • Setters don't have initializers. If you changed that to std::is_assignable and {m_name = std::forward<T>(Name);}, then it'd still be wrong, since you defined string@ as perfect forwarding of something constructable to a std::string, not assignable. As I said in the answer, int to std::string is the counterexample here. Oct 30, 2012 at 19:10
3

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
  • Also happens all the time if you don't know in advance what the type of the data member is. Like a container. Or an algorithm. Oct 30, 2012 at 18:43
  • Is no problem, because they all require movable.
    – Scrubbins
    Oct 30, 2012 at 18:45
  • @Scrubbins: It's not "completely wrong" (if you want to use this style, I'd say "You're completely wrong"), unless you think this answer is completely wrong: stackoverflow.com/a/10472297/1629821
    – Mr.C64
    Oct 30, 2012 at 18:45
  • The answer doesn't recommend this style at all. It merely suggests both options. It doesn't suggest which is preferable or why or when.
    – Scrubbins
    Oct 30, 2012 at 18:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.