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?