std::sink<T> - always pass sink arguments efficiently

320 views
Skip to first unread message

vittorio....@gmail.com

unread,
Sep 10, 2013, 9:42:08 AM9/10/13
to std-pr...@isocpp.org
Inspired by my own StackOverflow question.

Problem: passing sink arguments efficiently requires either a lot of code duplication or performance trade-offs.

Thanks to move semantics in C++11, we can now efficiently move large objects. 
In many occasions we require to pass a sink argument, which is an argument that will get stored somewhere, or it will be used then immediately discarded.

Here's the most common example: initializing a class instance with an std::string.



Approach 1: pass by value + std::move
struct Example {
    std
::string s;
   
Example(std::string mS) : s{std::move(mS)} { }
};
If mS is a temporary: the value is initialized from the temporary by moving, and then you yourself move from the argument, thus no copy is made.
Result: 1 move

If mS is an lvalue: you copy it in the argument and then move from it, thus a single copy + move is made.
Result: 1 copy + 1 move



Approach 2: have two constructors (one by const&, one by &&)
struct Example {
    std
::string s;
    Example(std::string&& mS) : s{std::move(mS)} { } // ctor 1
   
Example(const std::string& mS) : s{mS} { }       // ctor 2

};
If mS is a temporary: ctor 1 is called. No copy is made. Only the move constructor for mS is called.
Result: 1 move

If mS is an lvalue: you copy it in the argument, thus a single copy is made.
Result: 1 copy

Additional benefit:
No additional code is generated on the caller's side! The calling of the copy- or move-constructor (which might be inlined or not) can now live in the implementation of the called function (here: Test::Test) and therefore only a single copy of that code is required. If you use by-value parameter passing, the caller is responsible for producing the object that is passed to the function.



As you can see, Approach 2 has the most efficient results and generates the best possible code. It has one major drawback: code duplication.
You have to write n^2 constructors for all possible combinations of const& and &&.
struct Example {
    std
::string s1, s2;
    Example(std::string&& mS1,      std::string&& mS2)      : s1{std::move(mS1)}, s2{std::move(mS2)} { }
   
Example(const std::string& mS1, std::string&& mS2)      : s1{mS1}, s2{std::move(mS2)} { }
   
Example(std::string&& mS1,      const std::string& mS2) : s1{std::move(mS1)}, s2{mS2} { }
   
Example(const std::string& mS1, const std::string& mS2) : s1{mS1}, s2{mS2} { }
};



Proposal: add std::sink<T>, which automatically performs the best possible action, depending on whether T is a const& or an &&

struct Example {
    std
::string s1, s2;
    Example(std::sink<std::string> mS1, std::sink<std::string> mS2) : s1{mS1}, s2{mS2} { }
};

It should behave exactly as if four constructors (n^2) were written, like in the Approach 2 example.
std::sink<T> would allow the developer to not call any unnecessary constructors and to not write n^2 constructors/functions. No performance trade-off, no code duplication.

Thoughts?

Billy O'Neal

unread,
Sep 10, 2013, 2:01:58 PM9/10/13
to std-proposals
Again, my concern is that I don't think this is implementable. In particular, your proposed syntax requires that sink change its type, which is a compile time only construct, at run time. You would probably need to define some new feature in the core language to support this, and that's going to be the sticking point.

Billy O'Neal
Malware Response Instructor - BleepingComputer.com


--
 
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.

Vittorio Romeo

unread,
Sep 10, 2013, 3:00:24 PM9/10/13
to std-pr...@isocpp.org
This is a duplicate thread - I submitted it earlier but for some reason it didn't appear until now.
Feel free to delete it / merge it with the other one if possible.

I understand your concern, but this feels like a language defect honestly. It makes no sense that you're forced to write 2^n constructors to achieve best efficiency.


From: billy...@gmail.com
Date: Tue, 10 Sep 2013 11:01:58 -0700
Subject: Re: [std-proposals] std::sink<T> - always pass sink arguments efficiently
To: std-pr...@isocpp.org

Jared Grubb

unread,
Sep 10, 2013, 5:23:49 PM9/10/13
to std-pr...@isocpp.org


On Tuesday, September 10, 2013 6:42:08 AM UTC-7, Vittorio Romeo wrote:
Inspired by my own StackOverflow question.

Problem: passing sink arguments efficiently requires either a lot of code duplication or performance trade-offs.

Thanks to move semantics in C++11, we can now efficiently move large objects. 
In many occasions we require to pass a sink argument, which is an argument that will get stored somewhere, or it will be used then immediately discarded.

Here's the most common example: initializing a class instance with an std::string.



Approach 1: pass by value + std::move
struct Example {
    std
::string s;
   
Example(std::string mS) : s{std::move(mS)} { }
};
If mS is a temporary: the value is initialized from the temporary by moving, and then you yourself move from the argument, thus no copy is made.
Result: 1 move

If mS is an lvalue: you copy it in the argument and then move from it, thus a single copy + move is made.
Result: 1 copy + 1 move

At GoingNative last week, someone mentioned that "move elision" will (probably?) be added to the standard. That means the compiler is allowed to remove moves in the same way compilers are allowed to remove copies. 

I think that would apply to your example, and so the results would just be "1 move" and "1 copy", respectively. But, ask a guru, since I'm not 100% certain.

Jonathan Wakely

unread,
Sep 11, 2013, 9:28:57 AM9/11/13
to std-pr...@isocpp.org

On Tuesday, September 10, 2013 10:23:49 PM UTC+1, Jared Grubb wrote:

At GoingNative last week, someone mentioned that "move elision" will (probably?) be added to the standard. That means the compiler is allowed to remove moves in the same way compilers are allowed to remove copies. 


That's already allowed in C++11.

However, this is a duplicate thread, let's keep replies in the other one so the discussion doesn't split in two, please.
Reply all
Reply to author
Forward
0 new messages