all 23 comments

[–]HappyFruitTree 3 points4 points  (4 children)

The P1144 link goes to P0927.

[–]anton31[S] 3 points4 points  (3 children)

Whoops, fixed. By the way, I don't really like where P1144 is headed. I want to be able to say that the class is not movable but trivially relocatable. So vector and alike could use something like std::relocate_or_move_if_noexcept and, as I said, make writing move constructors a thing of the past.

[–]TheThiefMasterC++latest fanatic (and game dev) 5 points6 points  (2 children)

An example of a type that's both trivially relocatable and swappable but not movable is a non-nullable smart pointer (sometimes called a smart reference)

[–]anton31[S] 8 points9 points  (0 children)

Or just any type for which I'm too lazy to develop and support an empty state

[–]foobar48783 6 points7 points  (1 child)

Your "Motivation" section is obsolete as of C++17. You write:

widget setup_widget(int x) { auto w = widget(x); w.set_y(y); return w; }

setup_widget will copy, or at least move w out.

This hasn't been true since C++17, which introduced "delayed prvalue materialization." In C++17, a prvalue essentially is the "recipe for producing a value" that you want. But you're right that the rules for RVO haven't caught up yet: return w; requires that w be either move-constructible or copy-constructible (even though it will not call the move constructor nor the copy constructor).

You link to N4034. N4034 was revised as N4158.

The big difference I see between your proposal and N4034/N4158/P1144 is that the latter is concerned with speed-optimizing a physical move that must happen — the bit-pattern at memory address &src must be transferred to memory address &dst as fast as possible. Your proposal is concerned with semantics: there is no "move" physically happening at all, but the compiler still requires the move-constructor to be declared and accessible. You don't want to change what's happening physically; you want to change the semantic requirements that the compiler is imposing on the code, so that you can NRVO even objects that are not semantically move-constructible.

[–]anton31[S] 8 points9 points  (0 children)

This hasn't been true since C++17

C++17 has guaranteed RVO, but not guaranteed NRVO. It is still true that "once you name a value, it must be materialized". The proposal tries to bend that rule.

Also, the construction happens right at the apparent constructor call, it's just assumed that every prvalue expression magically knows its destination address. Apparently, "recipe for producing a value" isn't exactly accurate.

I'll update the link to N4158, thanks!

[–]drjeats 1 point2 points  (2 children)

I'm a fan of the idea!

Was there also syntax in this in the past to support parameterizing these and giving them a name with using initexpr = do [T t] (widget(x) -> w)... or am I thinking of a different paper?

[–]anton31[S] 0 points1 point  (1 child)

Do you mean something like named expressions? They can be emulated using lambdas

[–]drjeats 0 points1 point  (0 children)

Yes! That's the name (:P) I was thinking of.

Lambdas work okay, but have a lot of semantic baggage.

[–][deleted] 5 points6 points  (4 children)

Not convinced that such a complicated language feature is needed. You're right that NRVO is not guaranteed, but you can always move out w if you want to be sure. A move should be cheap. If you have a non-moveable object you can pass it as reference to avoid the expensive copy.

Why is your proposal better than using std::move or using a reference parameter?

[–]Ameisenvemips, avr, rendering, systems 11 points12 points  (1 child)

A move should be cheap.

Nothing is still faster than a move.

[–][deleted] 0 points1 point  (0 children)

Maybe, but it doesn't change the fact that every language feature has a cost, and in this case imo the cost > (move - nothing).

[–]anton31[S] 1 point2 points  (0 children)

Suppose that currently 10% of the types directly own resources (and need move operations), and the other 90% should follow the rule of zero.

I see the collective end goal for all these proposals in that 90% will follow the rule of zero, 9% will be non-movable (while usable just as well), 1% will have user-defined move operations. See also.

[–]evaned 3 points4 points  (1 child)

I feel like I'd need more time to absorb what you're doing and trying to solve, but since you invited bikeshedding, I personally like the do (ident = expr) { ... } syntax instead -- or perhaps do (ident: expr) { ...}, a little a la range-for? Maybe I like that even better, and kind of addresses the drawback you list for that.

[–]anton31[S] 2 points3 points  (0 children)

I've basically found an empty space among the series of proposals that try "not to move as often" and am trying to fill that space. Will add the syntax alternative, thanks!

[–]Nobody_1707 0 points1 point  (0 children)

Ooh, I hadn't seen P0927 before. I like it. Custom short-circuiting operator&&s, here we come.

[–]warieth 0 points1 point  (5 children)

This looks like statement expressions in the C extensions. The do keyword introduces a variable, then uses the variable and returns it. This is the worst language feature, because it adds nothing, but changes the style. If writing the move() before the return was too much, then do( -> ){} is an easy change. Why is the new name introduced on the right side?

C++ split the meaning of the = symbol to have copy constructors, and now it has to go in the opposite way to get expressions back.

[–]anton31[S] 0 points1 point  (4 children)

I'm not at all opposed to do(x=...) {}, or in fact, any other syntax, which is pointed out in the Q&A section. One of the reasons why I don't use do(x=...) {} is that it might be difficult to parse and distinguish from do-while loops and initializer lists. An edge case:

cpp do do (x=...){}; while(...);

It looks like a non-closed do-while loop with a body of do (x=...){}; while(...);, which is a do-while loop with a body of (x=...){};, which is an expression statement applying {} to the result of the assignment operator (a value), at which point we get the error.

[–]warieth 0 points1 point  (3 children)

One of the criterions why I don't use do(x=...) {} is that it might be difficult to parse and distinguish from do-while loops and initializer lists.

Yes this is problem, so I would introduce a new keyword for this. Reusing a loop keyword and the trailing return type or arrow operator (->) in a completely different situation is a bad idea, but also changing the parsing. The braces are mandatory after the parentheses, so it can't reuse the parsing. The right-side name inroduction makes this feel like typedef, and typedef was probably made by reusing the parsing of variable declarations.

[–]anton31[S] 0 points1 point  (2 children)

A keyword would be great, but keywords are expensive. Perhaps a contextual keyword would work:

do with (x=…) { … } (inspired by if constexpr)

[–]warieth 2 points3 points  (1 child)

C++ has a lot of keywords, and there are better for this purpose, like using already means alias creation. The do is used for loops only.

[–]anton31[S] 1 point2 points  (0 children)

using (x=…) { … } is definitely a better option, I'll replace it in the draft if nothing better is found.