In summary, if you're writing a C API/ABI, source compatibility is still a major advantage, there is no two ways around it.
205 karma · joined May 9, 2015
In summary, if you're writing a C API/ABI, source compatibility is still a major advantage, there is no two ways around it.
If you don't have any friends with different political views, this is an unlikely enough occurrence via chance that it seems like it's a deliberate choice, and that's not a choice that I have anything good to say about.
Also, realistically if you hire many programmers who only speak English, it's simply not realistic to hire a programmer who speaks French exclusively because he can't communicate with the team. Putting "copiez" on your photocopier does not alleviate that.
These laws are discrimination, pure and simple, and have been found as such by the Canadian Supreme Court on many occasions. Most Quebecois just don't care about paltry things like individual rights, where their language is involved.
Quebec is a joke, both economically and with respect to liberal democracy.
Classic example is logging an error on failure. This means calling a logging function in the catch block, and then letting the exception propagate. But what if the call to the logging function fails? In Java, coded naively you'd simply drop the original exception. Usually that's not what you want.
You can examine the issue with error codes, it's not any better.
These differences are already used very widely in discussions about policy, as you noted, and that's exactly part of the reason why it should be fair game to fully investigate those differences.
The thing is, who should solve it? The different approaches you listed have different advantages, there isn't one right answer. If the language itself solves it for you, you are stuck with whatever solution the language picked. This would be fine in a higher level language, but not in C++.
You're right though that individual devs shouldn't be solving it, it should be in a library. If there's enough interest in these posts, I'm happy to put up my work (fully fleshed out and documented) on a github for people to use.
In generally I tried to avoid an overly negative tone when referencing your code or statements, I hope you find that it's reflected in the post.
I think that a "combined type", can be safer if well written than a raw pointer. Surely you don't disagree with that? That's not the same as saying all combined types are better.
Similarly, adding abstraction can be good, it can also be bad. C adds abstraction to assembly. I'm guessing though that you would choose C over assembly for many things?
Repeating yourself is a bad thing in general. It could be that to get rid of the repetition we'd have to incur other costs that might be worse. I don't think that's the case here.
I just don't understand your "magical create_contiguous_memory" comment. Do you feel like the sort function is magic, and instead we should write all of our sorts out at the call site? More practically, we write a good sort once, document it carefully. The users of sort know roughly how it works and exactly how to use it. In the rare cases they care, they read the code. make_contiguous is exactly in this boat: you write it once, you write it carefully, and then you use it without worrying about the details every single time. Our brains are just too small to deal with all the details all the time, that's why we're trying to hide complexity that isn't as immediately relevant.
Your post is mostly sweeping generalities which aren't true in general, and as far as talking about the code goes, you mostly just say you don't like it or find it hard to read, and brush aside anything concrete like reusability, testability, bounds checking, etc, specific things that are actually useful.
From an ok, not great programmer with a year's experience, I wouldn't necessarily expect them to fully understand make_contiguous right away. But I would expect them to understand who owns the memory, and the memory layout.
I guess we'll agree to disagree, I think that C++ gives us some nice ways of writing it, I think that's what the post shows :-)
I think I did give it a "little" discussion :-). Guess it depends on your definition of a little. I didn't want to talk more about it because I didn't want to get sidetracked, and this is ultimately the way I chose to do it.
Hope that sheds some light on the post and the choices I made with it.
The thing about templates is misleading. Sure, if misused they can hurt your cache. But they also move branching from run time to compile time, which is a very good thing. Google some benchmarks of C++ sort vs C qsort; the former is much faster because the comparator can easily be inlined.
The templates used in my post, for example, either bloat the code not at all, or very little. They are either generating tiny functions that get inlined anyway (like ArrayView; once a function is inlined its irrelevant whether it came from a template or not for code bloat purposes) or they are generating code that would just need to be written by hand (like make_contiguous).
I've actually personally witnessed two fairly detailed accounts of people that actually got themselves into situations where template or template-like bloat became a performance negative, relative to the benefits they provided. They didn't cite a cliche about code bloat, they actually benchmarked and found the cost. What both of them were doing was very extreme; nobody who's not using very extreme template techniques (and therefore is sold on it) is hitting that point.
I think it's risky to say that it's misguided without the full context of the problem.
If you want a concrete example, consider std::stringstream. You agree this class is useful, right? In many cases, you build up a string, bit by bit. You then want to extract the string at the end. Because stringstream was written before move semantics, that string is returned by value. That means a full copy of the internal string buffer is made.
In 95% of real life use cases of stringstream, the stringstream is a local variable used to build up a string and then discarded. So copying that string is fundamentally a waste. But exposing the string buffer in a way that std::move could be fruitfully called on it is dangerous, it may break stringstream's class invariants and turn it into a toxic object, this is avoided in C++. So the preferred solution would be to give stringstream::str an rvalue overload, so that the stringstream can safely release its string buffer. Does that make sense?
Of course I am aware that this code involves extra work, is harder for less experienced team members to understand, etc. I wouldn't do it in the vast majority of situations. But sometimes it is useful. The goal was to try to help people understand &&/& member overloads, as there isn't much discussion of them.
Respectfully, I am a professional c++ developer. Odds are that the code I write is under greater pressure for performance and genericity. Maybe a bit of benefit of the doubt is in order.
It seems like the example I picked was a bit unfortunate in the following sense: a couple of people have already misinterpreted the point of the article to be that foo still had content after I moved its string (I won't use stole anymore, seems like a touchy subject).
That's not the point, the point is that the invariants are maintained, which is the requirement for a valid state. I could have reset m_set at the same time as I set m_cached to false, it would still be just as correct.
Edit: I have modified the post slightly so that it's a bit clearer that what I'm after is a valid state for foo, not to maintain the specific things that were inserted.
"By stealing the string, we put foo in an invalid state".
foo is an object of the SetStringer class. It puts it an in invalid state because it no longer maintains its own invariants.
The whole point of this article is about how you can move member variables for efficiency, while ensuring the owning object is left in a valid state (as move/rvalue semantics require).
Wanting to be able to move a member variable is no stranger than wanting to move the object itself; in that sense an rvalue ref overloaded getter is no stranger than move construction/assignment, it's just more rare because it's less used.
I just think it's a bit amusing, people complain about this sort of thing in C++ all the time. I can imagine if Rust takes over people will complain how you have no idea what u = v is doing unless you know what u and v are.
I'll admit my biases, but I prefer the c++ way: types know how to both move and copy themselves, and they will copy by default, but move when it's safe (rvalue) or explicitly asked to (std::move). I like looking at auto u = v and knowing that u is always a copy of v.
Rust hasn't seen enough widespread use to find all the problems and limitations with the language. The borrow checker seems great, sure. But looking at how Rust handles moves vs copies, I'm not convinced at all that it will be good enough in the long run.
This "mess" is caused by imbuing Example with correct move and copy semantics. In rust, it seems like most types have one or the other, but not both. If a type does not implement Copy, then when you try to copy it, it automatically gets moved from. So u = v would change the value of v. Ironically, that's similar to the behavior of auto_ptr that you just criticized, and quite unintuitive.
If you're claiming that the verbose way is just writing out the special functions, then I can assure you that it is not easier to maintain. If you are saying that the class in question could simply write a nested class that wrapped unique_ptr and had the correct copying behavior, maybe, there are arguments for both.
This example is fully intended to be solid code for production use, not primarily to be "clever".