3
\$\begingroup\$

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);
\$\endgroup\$
9
  • 1
    \$\begingroup\$ Whether the syntax of your assignment idiom is better than std::tie is really questionable. Moreover, in c++17, structured bindings are much more neater. But +1 for use of lambda, I love lambdas in c++ they’re so op. \$\endgroup\$ Feb 25, 2021 at 21:51
  • \$\begingroup\$ structured bindings for assignment? How? \$\endgroup\$ Feb 25, 2021 at 22:18
  • \$\begingroup\$ One way: auto [a, b, c] = std::array<int, 3>{1,2,3}; \$\endgroup\$
    – Edward
    Feb 25, 2021 at 22:47
  • 2
    \$\begingroup\$ @SᴀᴍOnᴇᴌᴀ fwiw, I believe OP's question is OK (if brief, and looking for "what do you think" feedback instead of code review). The "a,b,c" business is just sample usage, like a unit test. The 9-line snippet at the top of the question really is valid C++ — the ... in it are real C++17 syntax. \$\endgroup\$ Feb 25, 2021 at 23:20
  • \$\begingroup\$ @user1095108, could you clarify the line about causing a segfault in GCC? Is that a current development GCC, or an older release? If the former, is there a bug report for it? I find it's very rare to crash the compiler, largely because of good bug-reporting. \$\endgroup\$ Feb 26, 2021 at 7:44

2 Answers 2

3
\$\begingroup\$

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.)

\$\endgroup\$
1
  • \$\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\$ Feb 25, 2021 at 23:32
2
\$\begingroup\$

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.

\$\endgroup\$
8
  • \$\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\$ Aug 3 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
    Aug 4 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\$ Aug 4 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
    Aug 4 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
    Aug 4 at 15:24

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.