I guess we all know how to swap values, but there is another way, the assign idiom:
constexpr auto assign(auto& ...a) noexcept
{
return [&](auto const ...v) noexcept { ((a = v), ...); };
}
To swap, we need to invoke assign(a, b)(b, a)
. Is this a good way to swap values for fundamental types? Does it offer more room for optimizations than the usual way?
CodePudding user response:
Is this a good way to swap values for fundamental types?
No it is not. As people are pointing out, there is no assign idiom. I recommend that you use std::swap
instead, so folks reading your code will understand, without having to google.
CodePudding user response:
Since you keep asking for theoreticals in the comments: Compiler optimizations are based around whether the compiler can see common patterns to transform them into better code based on whatever the optimization metric is.
To that end, the compiler can be really smart at figuring out that the following is a swap:
// Conventional (simplified) swap definition
auto swap(int& a, int& b) -> void {
const int tmp = a;
a = b;
b = tmp;
}
In the code you provide, it should be almost equivalent for fundamental types only, since it will see the parameters const auto...v
as the tmp
object -- however it will see two sets of parameters for the copies. If we flatten a transformation of assign(a,b)(b,a)
, what the compile really sees from the template expansion is:
const auto v1 = a;
const auto v2 = b;
b = v1;
a = v2;
It's a similar expression to the standard swap
, but not quite the same as to what compilers have been trained to recognize for several decades of optimizations. Most likely, the compilers will see this as an equivalent transformation, and produce the same assembly -- which is the case with both gcc
and clang
(Credit to @HolyBlackCat for the Godbolt link).
Please note that compilers are really smart at optimizing code written in conventional/expected ways. What they tend to dislike and struggle with is attempts to be clever. In particular, assign(a,b)(b,a)
requires the compiler to flatten the inputs to make the optimization in the first place -- whereas the conventional swap(a,b)
is spelt out for it. Basically: at best you will get the same as just doing things the conventional way.
To that end, this is not a good way to swap values. This likely does not provide better optimizations (if anything, likely slightly worse).
If we expand this definition to include generics, it gets worse since the const auto
provides more copies, and does not perform proper moves (and proper move-semantics also help the compiler as well). Additionally it doesn't semantically read as a "swap
", whereas swap(a,b)
or even std::tie(a,b) = std::make_tuple(b,a)
is much less ambiguous.
Also this is not an "idiom" as this has never been established by usage as having been a pattern.
CodePudding user response:
I think it makes some sense if you were assigning multiple values at once, where some of the assignee(not sure that's the term?) might be changed in the process.
For instance doing a rotate:
int a = 10, b = 20, c = 30;
a = b;
b = c;
c = a; // oops you might wanted c = 10, but c is actually 20 now.
However, if you are only swapping 2 values, then std::swap
is probably the better way to go, in both readability and optimizability.