Inspired by my own StackOverflow question: Should I always move on sink constructor or setter arguments?
std::move
struct Example { std::string s1, s2; Example(std::string mS1, std::string mS2) : s1{std::move(mS1)}, s2{std::move(mS2)} { } };
Pros: no code duplication.
Cons: unnecessary move when the passed argument is an lvalue.
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} { } };
Pros: most efficient behavior and code generation.
Cons: requires n^2 constructors/functions written by the developer!
std::sink<T>
by value - behaves identically to Approach 2, avoids code duplicationstruct Example { std::string s1, s2; Example(std::sink<std::string> mS1, std::sink<std::string> mS2) : s1{mS1}, s2{mS2} { } };
The above code behaves as if the user had written n^2 constructors similarly to Approach 2.
Thoughts?
Perfect forwarding was added to the language in order to solve the N^2
> How does perfect forwarding help here? Or is it an issue?
overloads problem. Of course it can help here.
Yeah, but it is a bit painful to use, as you end up needing to make all the parameters templates and then use SFINAE or Concepts Lite to limit the damage.
Cons: requires n^2 constructors/functions written by the developer!
--
---
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/.
--
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.
That's already allowed in C++11.
--
The standard doesn't allow move elision from xvalues (as described at G::N). That is, givenexpensive_t go(){
expensive_t result;return move(result);}Implementations aren't allowed to use RVO/NRVO here for the result of std::move(), even though this would normally be an NRVO candidate.
The standard doesn't allow move elision from xvalues (as described at G::N). That is, givenexpensive_t go(){
expensive_t result;return move(result);}Implementations aren't allowed to use RVO/NRVO here for the result of std::move(), even though this would normally be an NRVO candidate.
I'm confused: is the compiler able to elide or not in this situation?
That's `traditional` move/copy elision in my book - the typical elision done on return from functions.
I'd say it's not a simplification of my examples but a completely different thing.
So I guess not having move-elision on `sink` arguments can still be considered a `defect`.
That's `traditional` move/copy elision in my book - the typical elision done on return from functions.
If the idea has any merit, I would prefer "in" over "sink" because arguments dont necessarily sink (like in unique-ptr).
Thanks,
Sumant
void singleSinkFunc(std::sink<ExpensiveType> a) { doSomething(a); }
// v--- TRANSLATES TO ---v
void singleSinkFunc(ExpensiveType&& a) { doSomething(std::move(a)); }
void singleSinkFunc(const ExpensiveType& a) { doSomething(a); }
void doubleSinkFunc(std::sink<ExpensiveType> a, std::sink<ExpensiveType> b) { doSomething(a, b); }
// v--- TRANSLATES TO ---v
void doubleSinkFunc(ExpensiveType&& a, ExpensiveType&& b) { doSomething(std::move(a), std::move(b)); }
void doubleSinkFunc(const ExpensiveType& a, ExpensiveType&& b) { doSomething(a, std::move(b)); }
void doubleSinkFunc(ExpensiveType&& a, const ExpensiveType& b) { doSomething(std::move(a), b); }
void doubleSinkFunc(const ExpensiveType& a, const ExpensiveType& b) { doSomething(a, b); }
void sinkFunc(std::in<ExpensiveType> a);
void sinkFunc(>ExpensiveType< a);
void sinkFunc(ExpensiveType<< a);
void sinkFunc(ExpensiveType sink a);
void sinkFunc(sink ExpensiveType a);
void sinkFunc(ExpensiveType in a);
void sinkFunc(in ExpensiveType a);
--
---
You received this message because you are subscribed to a topic in the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this topic, visit https://groups.google.com/a/isocpp.org/d/topic/std-proposals/flqtAYA-yMI/unsubscribe.
To unsubscribe from this group and all its topics, send an email to std-proposal...@isocpp.org.
--
---
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.
>Not if Richard's NB comment 12 (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3733.pdf, search for "US 12") is properly resolved - which allows copy / move elision from xvalues.Fair enough. :) But at the moment it isn't resolved (and isn't resolved for C++11), so I'm going to keep giving that advice until it changes.
--
struct Example {
std::string s1, s2;
Example(sink std::string mS1, sink std::string mS2) : s1{mS1}, s2{mS2} { }
};
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} { }
};
I'm resurrecting this after the awesome talk Herb Sutter gave at CppCon 2014, which covered in depth the issue of "sink" parameters.
In the talk, he showed benchmarks on the various "conventions" used to pass sink parameters.
Passing by value, then moving or passing by const reference can become very inefficient in some situations.
The known solutions are either creating every possible 2^n combination of parameters or using a template + perfect forwarding.Both of these solutions are problematic: the first one requires an incredible amount of code duplication, the second one forces the programmer to use templates and it's hard to get right.
I can't help but feel that this is something that needs to be fixed in the language.
Therefore I, again, propose a new syntactic sugar argument-passing symbol that generates code equivalent to the 2^n solution.
Writing
struct Example {
std::string s1, s2;
Example(sink std::string mS1, sink std::string mS2) : s1{mS1}, s2{mS2} { }
};
is equvalent to writing
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} { }
};The `sink` syntax is just an example - I'm sure it could be much better. `std::sink<T>` could be used, or T&&&, or something completely different.
Thoughts?
Is this issue being looked into by the language evolution team? Should I try writing an official proposal and submit it?
--
I'm resurrecting this after the awesome talk Herb Sutter gave at CppCon 2014, which covered in depth the issue of "sink" parameters.
In the talk, he showed benchmarks on the various "conventions" used to pass sink parameters.
Passing by value, then moving or passing by const reference can become very inefficient in some situations.
The known solutions are either creating every possible 2^n combination of parameters or using a template + perfect forwarding.Both of these solutions are problematic: the first one requires an incredible amount of code duplication, the second one forces the programmer to use templates and it's hard to get right.
I can't help but feel that this is something that needs to be fixed in the language.Therefore I, again, propose a new syntactic sugar argument-passing symbol that generates code equivalent to the 2^n solution.
struct Example {std::string s1, s2;
Example(sink std::string mS1, sink std::string mS2) : s1{mS1}, s2{mS2} { }
};
The `sink` syntax is just an example - I'm sure it could be much better. `std::sink<T>` could be used, or T&&&, or something completely different.
Thoughts?
Is this issue being looked into by the language evolution team?
Should I try writing an official proposal and submit it?
Convenience and micro-optimization don’t go hand in hand. Suffering the inconvenience of adding all the reference overloads, all the time, just for performance, is one’s own fault. If it weren’t for the correctness issue I mentioned, I’d see a lot less motivation for any new feature.
Which is why you should avoid passing by value anything that requires doing
copies to temporary memory due to the ABI. Don't pass by value anything larger
than 16 bytes or which is not trivially copyable.
On 2014–09–19, at 10:35 PM, Vittorio Romeo <vittorio....@gmail.com> wrote:I'm resurrecting this after the awesome talk Herb Sutter gave at CppCon 2014, which covered in depth the issue of "sink" parameters.
In the talk, he showed benchmarks on the various "conventions" used to pass sink parameters.What conclusion or recommendation was made in the talk?
the recommendation is to use the exact same parameter passing conventions common to C++98 but with the addition of using an rvalue-reference overload for expensive-to-copy/cheap-to-move types that the callee is planning to keep ownership of.
Some other considerations and cases were brought up, the gist of the argument was to just do things like you did in C++98 and to exceedingly rarely take things by value.
I have counter-points, but most of them are based on (IMO) safer container designs than the STL uses (e.g. never ever allow implicit copying of an expensive-to-copy type), so they're rather irrelevant to this conversation I think.
template<typename T, typename U>
concept bool IsConvertible = requires()
{
requires std::is_convertible<U, std::decay_t<T>>{};
};
void foo(IsConvertible<std::string>&&s) { /* ... */ }
template<typename T>
requires IsConvertible<T, std::string>
void foo(T&& s) { /* ... */ }