Live data from Hacker News

The repercussions of missing an Ampersand in C++ and Rust

nablag.com

81–90 of 128 posts

Re: The repercussions of missing an Ampersand in C++ and Rust

#81
post #6

> I was specifically inspired by a performance bug due to a typo. This mistake is the “value param” vs “reference param” where your function copies a value instead of passing it by reference because an ampersand (&) was missing ... This simple typo is easy to miss the difference between `const Data& d` and `const Data d` isn't accurately characterized as "a typo" -- it's a semantically significant difference in inten…

The fact that implicit copies are a feature doesn't mean they were a good design choice to begin with. In new code I've started making the copy constructor explicit whenever I can, for instance, just to avoid this kind of shenanigans

Re: The repercussions of missing an Ampersand in C++ and Rust

#82
post #17

Earlier quoted context omitted.

If the implications of a one char diff are this egregious that they’re considered obvious, maybe it should take less cognitive effort to spot this? CI and tooling are great, but would be far less necessary if it was more difficult to make this mistake in the first place.

What do you suggest? Some kind of std::const_reference ? Clang-tidy is enough in addition to the reviews.

The problem is not the reference, the problem is implicit copies and the horses left the barn 40 years ago, it's too late to fix that. The only thing we can do right now is deleting or marking copy constructors explicit whenever possible

Re: The repercussions of missing an Ampersand in C++ and Rust

#83
post #40
post #22

Earlier quoted context omitted.

this is the defensive and correct C++ approach, anyways.

Isn't that just same old "skill issue", "No True C(++) programmer" refrain? If people could keep entirety of J.2 appendix in their mind at all time we would not have these issues. And if they had entirety of J appendix in mind all C code would be portable. Or if people just always ran -Wall -Wpedantic -Wall_for_real_this_time -fsanitize=thread,memory,address,leaks,prayers,hopes,dreams,eldritch_beings,elder_gods -fno-…

Yes, it is the old "skill issue" argument.

When your language is that unsafe and difficult to hold correctly, you have to make sure that you at least try your very best.

Re: The repercussions of missing an Ampersand in C++ and Rust

#84
The real issue is that C++ does implicit _deep_ copies by default on assignment and that you can't retrofit the language to change that. One quick, fast solution to avoid such shenanigans is to follow the one parameter `explicit` constructor rule religiously and always mark copy constructors explicit unless you know as a fact the type is trivially memcpy-able. This fixes most of the issues.

Another problem with C++ references is that they aren't really reference types, they are aliases, so they have wonky semantics and crazy nonsensical features like `const T&` doing lifetime extension

Re: The repercussions of missing an Ampersand in C++ and Rust

#85

Earlier quoted context omitted.

> This means C++ is riddled with types that have unrelated "I'm empty" state Again, these cases are still rare. Most classes either don't require user-defined move operations, or they have some notion of emptiness or default state. > And if we don't want to allow None? Too bad, you can't express that in C++ That's actually a good example! Nitpick: you can express it in C++, just not without additional logic and some…

>you can express it in C++, just not without additional logic and some overhead :) How?

E.g. with a boolean member or by setting a bit in the pointer value.

Re: The repercussions of missing an Ampersand in C++ and Rust

#86

Earlier quoted context omitted.

Your socket class would have no default constructor? And you would never want to close the socket before the object's lifetime ends? Really?

With destructive moves, you can end an object's lifetime whenever you want.

How would I use such a socket class as a member variable? How do I reopen the socket?

Re: The repercussions of missing an Ampersand in C++ and Rust

#87

Earlier quoted context omitted.

In practice, move operations typically just leave an empty object behind. The destructor already has to deal with that. And of course you can't call certain methods on an empty object. So in practice you don't need special logic except for the move operations themselves.

> The destructor already has to deal with that. That's partly true, partly circular. Because moves work this way, it's harder to make a class that doesn't have empty states, so I don't design my class to avoid empty states, so the destructor has to handle them.

It's not like it's the only part of the language that mandates a default constructor though. There are plenty of situations where default-constructible types are desirable. Even simple things like having a non-default-constructible type in a map is awkward.

Re: The repercussions of missing an Ampersand in C++ and Rust

#88
post #74

Earlier quoted context omitted.

If the moves were destructive, I'd design it to have the default constructor call `::socket` and destructor call `::close`. And there wouldn't be any kind of "closed" state. Why would I want it?

Your socket class would have no default constructor? And you would never want to close the socket before the object's lifetime ends? Really?

In this case, I would want the address family and protocol to be statically known, so it would have default constructor. But for example, a file might not have one, sure. As for closing before lifetime ends, why? I can just end lifetime. Wrap it in an optional if the type system can't figure it out like with a struct member.

Re: The repercussions of missing an Ampersand in C++ and Rust

#89

Earlier quoted context omitted.

With destructive moves, you can end an object's lifetime whenever you want.

How would I use such a socket class as a member variable? How do I reopen the socket?

Reopen by constructing and assigning a new socket.

Re: The repercussions of missing an Ampersand in C++ and Rust

#90
post #88

Earlier quoted context omitted.

Your socket class would have no default constructor? And you would never want to close the socket before the object's lifetime ends? Really?

In this case, I would want the address family and protocol to be statically known, so it would have default constructor. But for example, a file might not have one, sure. As for closing before lifetime ends, why? I can just end lifetime. Wrap it in an optional if the type system can't figure it out like with a struct member.

> so it would have default constructor.

And what's the underlying value of such a default constructed socket? I assume it would be -1 resp. INVALID_SOCKET, in which case the destructor would have to deal with it.

> Wrap it in an optional if the type system can't figure it out like with a struct member.

So you essentially must wrap it in an optional if you want to use it as a member variable. I find this rather pointless as sockets already have a well-defined value for empty state (-1 resp. INVALID_SOCKET). By wrapping it in a optional you are just wasting up to 8 bytes.

Sure, you can implement a socket class like that, but it's neither necessary nor idiomatic C++.

Post reply on HN