What do you think about this assignment "idiom"?
template <typename ...A>
constexpr auto assign(A& ...a) noexcept
{
return [&](auto&& ...v)
// noexcept(noexcept(((a = std::forward<decltype(v)>(v)), ...)))
{
((a = std::forward<decltype(v)>(v)), ...);
};
}
It seems a better alternative to me than the std::tie()
idiom. Uncommenting the comment could cause a segfault in gcc
.
Usage:
int a, b, c;
assign(a, b, c)(1, 2, 3);
2 Answers 2
This "idiom" seems strictly more confusing than the std::tie(a,b,c) = std::make_tuple(1,2,3)
idiom. (I mean, the latter is an idiom because multiple people use it and know it. Your thing doesn't count as an idiom yet because nobody uses or understands it.)
Your thing does save a lot of template instantiations — you don't have to instantiate std::tuple
at all, for example. That's potentially nice for people who are doing lots of multiple assignments in header files and care about compile times. But who's doing lots of multiple assignments in header files??
Your thing doesn't fix the most important shortcoming of std::tie
, which is that you can't use it to introduce a new variable. It's still fundamentally a std::cin
-style API, where the caller is supposed to define all their variables with garbage values and then use this API to mutate them into the desired state.
// To mutate three variables:
int a, b, c;
assign(a, b, c)(1, 2, 3);
std::tie(a, b, c) = std::tuple{1, 2, 3}; // same deal, easier to read, harder to compile
// To define three new variables (ish):
auto [a, b, c] = std::tuple{1, 2, 3};
"C++17 structured binding that also includes an existing variable" gives the skeleton of an API that could be used to define and mutate different sets of variables at the same time:
// To define one new variable and mutate two others:
auto [a] = AndTie(_1, b, c) = std::tuple{1, 2, 3};
But again, I wouldn't describe this as an "idiom", because if you showed it to an ordinary C++ programmer who knew all the C++ idioms, they likely still wouldn't understand it at a glance.
Oh, and a code-style nit: I would prefer to see your fold-expression with spaces around the ,
operator, to emphasize that it's an operator and not a separator. Like this:
((a = std::forward<decltype(v)>(v)) , ...);
and in fact as long as we're saving on function template instantiations, let's do
((a = static_cast<decltype(v)>(v)) , ...);
The inner set of parentheses is redundant, I think, but I'm keeping them. (Precisely because I had to say "I think." I'm not even gonna go find out. They're a good idea.)
-
\$\begingroup\$ In a professional setting, I can imagine, that my "idiom" would be a big "no no" :) But in my personal projects, I really hate many lines of assignments. The "idiom" seems to have something going for it, one liner assignments, certain to confuse the uninitiated and maybe there's room for improvement. \$\endgroup\$user1095108– user10951082021年02月25日 23:32:09 +00:00Commented Feb 25, 2021 at 23:32
Honestly, it's useless.
Unlike std::tie
, this assign
function only works when both the list of variables and the list of values are loose. It doesn't solve the major use case of std::tie
, which is to have multiple return values from a function.
assign(a,b,c)(table_lookup(s,t)); // error
In fact, the only use case I know of for your assign(a,b,c)(x,y,z)
, where anyone wouldn't just write
a=x;
b=y;
c=z;
is when the output list aliases the input list
assign(x,y)(y,x); // please swap, sir!
That line might really be written by someone more familiar with swap patterns in other languages than the C++ std::swap
function. Or when I can't use std::swap
because the new values contain a more complicated expression:
// backward traversal of Fibonacci!!!
assign(a,b)(b-a,a);
But in this case with aliasing involved, your function doesn't even work as desired.
At least with
x=y;
y=x;
I know that the values will not be swapped. With your function, the values still are not swapped, but I don't know that unless I look at the implementation.
If the assignments are taking too much vertical space, I can put multiple assignments on one line:
a=x; y=b; c=z;
It still is basic code understandable by even the least experienced programmer. And that actually uses fewer characters than invoking your multiple-assignment helper.
-
\$\begingroup\$ You're not thinking broadly enough, imagine target variables in a set on the left side and source variables in a set on the right side. We allow the compiler make the connections between sources and targets in a declarative sense. c++ is becoming a declarative language. True, we need to make a slight change and copy the source variables to make it so and I can't change my question. Food-for-thought though. \$\endgroup\$user1095108– user10951082022年08月03日 22:48:58 +00:00Commented Aug 3, 2022 at 22:48
-
\$\begingroup\$ @user1095108: I did imagine that, I included two examples in my answer, one for swap one for reverse Fibonacci. \$\endgroup\$Ben Voigt– Ben Voigt2022年08月04日 14:48:53 +00:00Commented Aug 4, 2022 at 14:48
-
\$\begingroup\$ the "idiom" came about after I needed some cheap (memory-wise) containers - just one example. I've debugged the resulting asm and I'm satisfied. I personally dislike having multiple assignments on one line like you showed and I like the work of the compiler, elimination of temporaries, ... If there are temporaries in play, the compiler knows best how to best optimize them. The traversal of an XOR list is a good example of a temp in play
assign(n, p)(n->link(p), n);
; p and n are swapped, n is reassigned and it's up to the compiler to make the best of it. \$\endgroup\$user1095108– user10951082022年08月04日 15:16:28 +00:00Commented Aug 4, 2022 at 15:16 -
\$\begingroup\$ I already gave an example of a temp in play which breaks your solution because of aliasing. Yes, it's possible to rewrite to make copies. As far as "I dislike having multiple assignments on one line" I don't understand how "multiple updates on one line via two calls to helper functions" makes that any better. The cognitive complexity is higher, first you have to decode what the helper is doing, then you still have to think about multiple variables changing. \$\endgroup\$Ben Voigt– Ben Voigt2022年08月04日 15:22:21 +00:00Commented Aug 4, 2022 at 15:22
-
\$\begingroup\$ @user1095108: And your example on github is insane because you use the identifier
assign
for two entirely different things, both being member functions of the same class! \$\endgroup\$Ben Voigt– Ben Voigt2022年08月04日 15:24:45 +00:00Commented Aug 4, 2022 at 15:24
auto [a, b, c] = std::array<int, 3>{1,2,3};
\$\endgroup\$...
in it are real C++17 syntax. \$\endgroup\$