C++ Stockholm Syndrome



  • @masonwheeler said in Go: The Good, The Bad, and The Ugly:

    @lb_ said in Go: The Good, The Bad, and The Ugly:

    Ironically, I prefer C++-style generics over Java-style generics.

    Meh. Java does generics badly, but not nearly as badly as C++.

    Four words: Turing-complete by accident. That's really all you need to know about the massive :wtf: that is C++ templates. It's bad generics and bad metaprogramming (which ought to be distinct concepts) lumped together into a big franken-mess.

    Oh yeah they're a mess, but they allow you to write generic code and do things that are literally impossible to do in other languages without runtime overhead. I'll take "possible without runtime overhead" over "less convoluted" any day. Of course if a C++ successor came along with less convoluted rules and better syntax and all the same abilities as template metaprogramming, you can bet I'd ditch C++ in that case.

    With Java generics, you cannot elect to not have a class member or function depending on the type of the generic. Instead you have to use workarounds like runtime overhead or inheritance or some design pattern. Java generics work fine in all the simple cases but they have no power to them.

    Don't even get me started on languages without static typing, I'll stick to having the compiler tell me I've made a mistake rather than QA or runtime errors. They're fully generic, sure, but that happens at runtime instead of compile time.

    @masonwheeler said in Go: The Good, The Bad, and The Ugly:

    @lb_ said in Go: The Good, The Bad, and The Ugly:

    From what I've read in his and others on his team's blog posts, they don't really have that firm a grasp on C++ anyway, they don't even like RAII.

    Sounds to me like they have a quite adequate grasp of C++ in that case. 🚎 RAII is an ugly abstraction inversion of a hack that would not be necessary if C++ had proper try/finally support. But this thread is supposed to be about Go, not C++, so I'll forbear from commenting further on that point here.

    What, so you want to actually remember to clean up resources all the time? What is your alternative to RAII? With C++ and RAII you can have zero-cost exceptions which are cheaper than C-style error handling and all memory is managed for you in centralized locations (constructors and destructors). You can even have custom allocators and deferred destruction if you really need to tune performance. The only thing try blocks should be for is recovering from an error in a sensible way (e.g. telling the user their file is corrupted).

    Garbage collection has its uses but I would never compare it to RAII or use it in the same situations - yes, they are both ways of managing memory, but garbage collection can't manage resources, so in languages like Java and Python you end up with try-with-resources language features, basically making up for lack of RAII.


    I honestly don't like C++ that much (compiling and linking is a horrible horrible mess that I absolutely despise and the template situation took me years to learn), I just cannot find any suitable replacement. I have most definitely investigated Rust and that's a whole mess that I'm letting simmer for now before making a final judgement, but the prospects are not good. So since I'm stuck with C++ maybe I just have stockholm syndrome, but here's some requirements for any replacement language:

    • enforced static typing (if typoInVairableName = 1 is not an error, get lost)
    • scope-based memory and resource management like RAII (gc is off by default)
    • const-correctness and immutable by default (e.g. mutable keyword)
    • exception handling (zero-cost, of course)
    • ability to annotate strength of exception guarantees (nice to have at language-level, though I guess documentation is an okay fallback)
    • as much zero-cost abstraction as possible (if it can be done at compile time instead of at runtime, it should be)
    • as much stuff checked by the compiler at compile time as possible (if it can be a compile error instead of a runtime error, it should be)
    • sane and consistent compiling/linking regardless of platform (dear god none of this shared-vs-static nonsense)
    • powerful generics and metaprogramming (I'm talking passing around and returning types as variables and first class citizens)
    • modern clean syntax (duh)
    • good standard library (duh)
    • UTF8 strings (duh)

    So far it seems I'm either too picky or people don't love and hate C++ enough to make it happen. Now, in the general sense when I'm just writing scripts or apps for personal use where performance isn't a concern, yeah I'll use any decent language - I'm not too picky in that case as long as it works and isn't annoying to use. But for serious projects and libraries I want people to actually use, I want everything on the above list, and even though C++ doesn't fit all the criteria, it's far closer than any other language I've seen.

    So what say you all? Am I crazy, and my list has stuff you don't like or agree with? Or do I have the right idea and you just feel C++ just has far too many drawbacks to be worth it to you?


  • Banned

    @lb_ said in C++ Stockholm Syndrome:

    Of course if a C++ successor came along with less convoluted rules and better syntax and all the same abilities as template metaprogramming, you can bet I'd ditch C++ in that case.



  • @Gąska

    I have most definitely investigated Rust and that's a whole mess that I'm letting simmer for now before making a final judgement, but the prospects are not good.



  • @lb_ said in C++ Stockholm Syndrome:

    But for serious projects and libraries I want people to actually use, I want everything on the above list, and even though C++ doesn't fit all the criteria, it's far closer than any other language I've seen.

    What projects are you running where a language like C# is "too slow"?

    I think you're just one of those people who are crotchety "GC is too slow!" even though you've never actually bothered to measure or quantify why it would be too slow in your projects.



  • @blakeyrat C# isn't too slow. It just has fewer bullet points on my wishlist than C++. Runtime performance isn't much of a concern for me, I let the compiler care about that. Of course, zero-cost abstractions are what enable the compiler to care in the first place.



  • @lb_ said in C++ Stockholm Syndrome:

    It just has fewer bullet points on my wishlist than C++.

    Well on purpose.

    They didn't implement metaprogramming for many very good reasons. (It does have full reflection, so don't worry you can still shoot your own foot if you want.)

    They don't have UTF8 support because... well arguably UTF8 was mature enough when it was designed, but it was still iffy and UTF16 was the more conservative choice.

    It has a WAY better standard library than C++, though. And is way better at compile-time checks.


  • Impossible Mission - B

    @lb_ said in C++ Stockholm Syndrome:

    Oh yeah they're a mess, but they allow you to write generic code and do things that are literally impossible to do in other languages without runtime overhead.

    Nope. Turing equivalence says otherwise. (If nothing else, you could simply write out the expansions longhand in other languages. Code generators frequently do the equivalent.)

    With Java generics, you cannot elect to not have a class member or function depending on the type of the generic. Instead you have to use workarounds like runtime overhead or inheritance or some design pattern. Java generics work fine in all the simple cases but they have no power to them.

    Now you're talking about metaprogramming. As I said, generics and metaprogramming are distinct things and should not be conflated. (Otherwise, you end up with the mess that is Templates.)

    What, so you want to actually remember to clean up resources all the time? What is your alternative to RAII?

    Hint: when someone says "abstraction inversion," they're not talking about using the feature exactly as expected; they're talking about all the things you can't do (or at least find much more difficult to do) because of the assumptions inherent in that expectation.

    With C++ and RAII you can have zero-cost exceptions which are cheaper than C-style error handling

    :rolleyes: The linked SO answer describes an ABI-level technique that has nothing to do with RAII or the C++ language. "Zero cost exceptions," exactly as described there, are the standard for pretty much every language on every OS on every 64-bit CPU.

    The only thing try blocks should be for is recovering from an error in a sensible way (e.g. telling the user their file is corrupted).

    So... yeah. You completely misunderstood what I said. Do you even know what a try/finally is, as opposed to what you're talking about (try/except)?

    I honestly don't like C++ that much (compiling and linking is a horrible horrible mess that I absolutely despise and the template situation took me years to learn), I just cannot find any suitable replacement.

    Just about anything, really. C++ may not be the worst language ever created, but without a doubt it is the worst ever to be taken seriously.

    I have most definitely investigated Rust and that's a whole mess that I'm letting simmer for now before making a final judgement, but the prospects are not good. So since I'm stuck with C++ maybe I just have stockholm syndrome, but here's some requirements for any replacement language:

    • enforced static typing (if typoInVairableName = 1 is not an error, get lost)

    So... any static language ever.

    • scope-based memory and resource management like RAII (gc is off by default)

    Like so many things, C++ takes a basically good idea and implements it in a really awful way. Non-memory resources are fundamentally not the same thing as memory allocation, so why force every resource to be handled as if it were a memory allocation? What I'd really like to see is Project Snowflake graduate from "research project" to "supported feature".

    • exception handling (zero-cost, of course)

    This is nothing special, see above.

    • ability to annotate strength of exception guarantees (nice to have at language-level, though I guess documentation is an okay fallback)

    So suddenly you want Java and checked exceptions, which C++ does not have? (Or am I misunderstanding what you're saying here.)

    • as much zero-cost abstraction as possible (if it can be done at compile time instead of at runtime, it should be)

    Nice to have. Even nicer when it's possible to debug the process by which they are generated, for those times when something inevitably goes wrong along the way. I'm not aware of any C++ compiler in which this is the case.

    • as much stuff checked by the compiler at compile time as possible (if it can be a compile error instead of a runtime error, it should be)

    So now you want Haskell or Ada? 🚎

    • sane and consistent compiling/linking regardless of platform (dear god none of this shared-vs-static nonsense)

    So now you want anything but C and C++, most likely the JVM or the CLR.

    • powerful generics and metaprogramming (I'm talking passing around and returning types as variables and first class citizens)

    Have a look at Boo. It has real generics and also powerful metaprogramming that's actually written in Boo rather than its own special ❄ syntax, and can be examined in the debugger just like any other executing code.

    • modern clean syntax (duh)

    Pretty much any language, depending on your definition of "modern" and "clean"

    • good standard library (duh)

    Again, the CLR or the JVM.

    • UTF8 strings (duh)

    This could be "any modern language" or "none of them," depending on whether you mean "has support for UTF8 encoding" or "strings are UTF8 by default."

    So far it seems I'm either too picky or people don't love and hate C++ enough to make it happen. Now, in the general sense when I'm just writing scripts or apps for personal use where performance isn't a concern, yeah I'll use any decent language - I'm not too picky in that case as long as it works and isn't annoying to use. But for serious projects and libraries I want people to actually use, I want everything on the above list, and even though C++ doesn't fit all the criteria, it's far closer than any other language I've seen.

    For libraries I want people to actually use, I want to publish them in something people are actually using, and with the way people have been abandoning C++ for managed languages for the past couple decades...

    So what say you all? Am I crazy, and my list has stuff you don't like or agree with? Or do I have the right idea and you just feel C++ just has far too many drawbacks to be worth it to you?

    You have a lot of good ideas, but I think your priorities are a bit skewed. The drawbacks are just far too serious for the advantages to be worthwhile.



  • C++ templates are amazing and I should really find a way to do this without exceptions and lambdas.


  • area_can

    @gąska I've debated learning Rust, but &mut_::unwrap<<the> _language: &looks>::like a C++ STL compilation error sometimes. that, plus it's still in development and the ecosystem is still in its early stages.


  • Banned

    @lb_ said in C++ Stockholm Syndrome:

    @Gąska

    I have most definitely investigated Rust and that's a whole mess that I'm letting simmer for now before making a final judgement, but the prospects are not good.

    Sorry, haven't noticed it at first glance in the wall of text I didn't read. But now that you mention it... What's your biggest issue with Rust? It has a high entry barrier and the web service part of ecosystem is a mess currently, but other than that it's a damn good language. I'd like to hear why you're averted by it.

    I was in pretty much the same situation as you a few years ago - looking for C++ that isn't C++. There were quite a few promising new languages in 2014, but so far Rust is the only sensible option.


  • Banned

    @bb36e said in C++ Stockholm Syndrome:

    @gąska I've debated learning Rust, but &mut_::unwrap<<the> _language: &looks>::like a C++ STL compilation error sometimes.

    But it has the coolest named operator of all languages - the turbofish!

    In all seriousness. Rust syntax is very symbol-heavy, but at least every part of it immediately makes sense. You get used to it eventually and then it reads very nicely, unlike some other symbol-heavy languages like Bash, Perl or whatever else people used before everything was in either C++ or Java.



  • @masonwheeler said in C++ Stockholm Syndrome:

    The linked SO answer describes an ABI-level technique that has nothing to do with RAII or the C++ language.

    I'd like to see you get zero-cost error handling without RAII. if(error){ free(data); } is slower because you have to branch on whether or not an error has happened. With RAII and zero-cost exceptions you have no such branches.

    @masonwheeler said in C++ Stockholm Syndrome:

    Do you even know what a try/finally is, as opposed to what you're talking about

    Yes, I'm talking about something different than you are because it's tangentially related, sorry for the confusion. I'm talking about when try should be used in my wishlist language. You're talking about a feature that my wishlist language doesn't need. I'm open to explanations of why finally would be useful in a language with RAII though.

    @masonwheeler said in C++ Stockholm Syndrome:

    so why force every resource to be handled as if it were a memory allocation?

    Actually, it's the other way around. Most (but not all, it's up to you) memory allocations are handled like resource acquisitions. That's what the RA in RAII is, right? And besides, all memory is scoped anyway, when the application ends the memory is returned to the OS. Treating it like something else is only useful in specific cases.

    @masonwheeler said in C++ Stockholm Syndrome:

    So suddenly you want Java and checked exceptions, which C++ does not have? (Or am I misunderstanding what you're saying here.)

    I'm talking about strong vs weak exception guarantees, aka "if an exception is thrown during this function, is my data as if the function call never happened, or is it in some between-but-valid state, or is it unrecoverable?" In C++ everything has at least the weak exception guarantee or higher, so you can be sure that everything is always in a valid state, though not necessarily a predictable state. The strong guarantee means that the data remains as if nothing even happened. I think this all applies to other languages with exception handling too, so you should know about it, maybe under a different name.

    @masonwheeler said in C++ Stockholm Syndrome:

    Even nicer when it's possible to debug the process by which they are generated, for those times when something inevitably goes wrong along the way. I'm not aware of any C++ compiler in which this is the case.

    Excellent point, I do realize that much of the abstraction and metaprogramming is difficult to debug because we don't really have compile-time debuggers. That's a definite barrier to entry.

    @masonwheeler said in C++ Stockholm Syndrome:

    So now you want anything but C and C++, most likely the JVM or the CLR.

    Actually you can avoid the static-vs-shared nightmare in C++ by just distributing source files and compiling all dependencies with your project at once. There are several open source libraries that support this consumption model, or you can do it manually, and it works reasonably well. That's basically what scripting languages do anyway, right?

    @masonwheeler said in C++ Stockholm Syndrome:

    Have a look at Boo.

    Last time I checked into it it looked like the Scala of C#, has that changed? I suppose it's better now that .NET can run on linux, but I've not researched that very much.

    @masonwheeler said in C++ Stockholm Syndrome:

    people have been abandoning C++ for managed languages for the past couple decades

    That's not the narrative I've been hearing. As far as I can tell C++ is becoming more popular thanks to the new standards.

    @masonwheeler said in C++ Stockholm Syndrome:

    I think your priorities are a bit skewed

    That's what I was worried about.



  • @ben_lubar said in C++ Stockholm Syndrome:

    C++ templates are amazing and I should really find a way to do this without exceptions and lambdas.

    You may want to look into std::variant and std::visit - usually it optimizes quite well.



  • @gąska said in C++ Stockholm Syndrome:

    now that you mention it... What's your biggest issue with Rust? It has a high entry barrier and the web service part of ecosystem is a mess currently, but other than that it's a damn good language. I'd like to hear why you're averted by it.

    Every time I look into it and read articles about it, there's some seriously fundamentally broken feature that makes everything moot. I recall at least two separate threads on these forums where something had gone horribly wrong and they were either going forward ignoring it or having to rewrite things. I'm waiting on it to settle down and mature some before I look at it again.

    Plus I'd really like to make a game and that looks harder in Rust than in C++.


  • Grade A Premium Asshole

    @masonwheeler said in C++ Stockholm Syndrome:

    Have a look at Boo.

    We have a contender to challenge the Blakey memes around here.


  • Grade A Premium Asshole

    @lb_ said in C++ Stockholm Syndrome:

    That's not the narrative I've been hearing. As far as I can tell C++ is becoming more popular thanks to the new standards

    Agreed. Maybe some new startups are trying out new shiny, but outside of that bubble C++ seems to me to becoming more popular in the last few years.


  • Banned

    @lb_ said in C++ Stockholm Syndrome:

    @gąska said in C++ Stockholm Syndrome:

    now that you mention it... What's your biggest issue with Rust? It has a high entry barrier and the web service part of ecosystem is a mess currently, but other than that it's a damn good language. I'd like to hear why you're averted by it.

    Every time I look into it and read articles about it, there's some seriously fundamentally broken feature that makes everything moot. I recall at least two separate threads on these forums where something had gone horribly wrong and they were either going forward ignoring it or having to rewrite things. I'm waiting on it to settle down and mature some before I look at it again.

    It would be easier for me to say something relevant if I knew what features exactly you're talking about. That said, Rust has gone a long way in the past year, adding lots of long awaited missing features. There's still a couple revolutions in sight (async/await, macros 2.0, coroutines, higher-kinded types), but it's mostly settled down. And borrow checker is much less annoying.

    Plus I'd really like to make a game and that looks harder in Rust than in C++.

    Everything is harder in Rust than in C++. Until you account for debugging effort.



  • @gąska Ah, that's good to hear. I'm especially happy Rust is getting coroutines. Are there any notable items on my wishlist Rust doesn't satisfy off the top of your head? I'm not concerned about the borrow checker, I can learn a new way to think and write code if it means being closer to my ideal language.


  • Impossible Mission - B

    @lb_ said in C++ Stockholm Syndrome:

    I'd like to see you get zero-cost error handling without RAII.

    "Zero cost" is a very deceptive term for what you linked to. What it actually described is table-based exception handling, which is an ABI-level way of setting up exception handling to avoid a lot of the runtime overhead of traditional SEH, at the expense of having to add a lot of extra metadata to your binary and making your ABI significantly more restrictive. (When Delphi implemented it for Win64, for example, they weren't able to do it without putting some very heavy restrictions on inline assembly that broke a lot of existing high-performance Delphi code.) It has nothing at all to do with RAII or the C++ language.

    if(error){ free(data); } is slower because you have to branch on whether or not an error has happened. With RAII and zero-cost exceptions you have no such branches.

    Yeah... no. What the "zero-cost" part actually means is that you can add exception handling to your code with zero overhead on the happy path. The actual exception-handling part (ie the "taking care of what to do when an error occurs" side of things) actually becomes more expensive, with the rationale being that you're supposed to be spending the vast majority of your time on the happy path anyway, so this is an acceptable tradeoff.

    Yes, I'm talking about something different than you are because it's tangentially related, sorry for the confusion. I'm talking about when try should be used in my wishlist language. You're talking about a feature that my wishlist language doesn't need. I'm open to explanations of why finally would be useful in a language with RAII though.

    For all of the same reasons it's useful in a language without RAII. The entire RAII mechanism is built on try-finally semantics, but the C++ language doesn't expose a try/finally mechanism to C++ developers, which forces you to implement try/finally scenarios that aren't related to memory/resource management in terms of RAII. (Which is the classic definition of an abstraction inversion.)

    Actually, it's the other way around. Most (but not all, it's up to you) memory allocations are handled like resource acquisitions. That's what the RA in RAII is, right?

    Not really. As has been pointed out plenty of times before in articles on the subject, RAII is a really bad name for describing what's going on, because it's really not about acquisition at all, but about getting release and cleanup right.

    And besides, all memory is scoped anyway, when the application ends the memory is returned to the OS. Treating it like something else is only useful in specific cases.

    ...wut?

    I'm talking about strong vs weak exception guarantees, aka "if an exception is thrown during this function, is my data as if the function call never happened, or is it in some between-but-valid state, or is it unrecoverable?" In C++ everything has at least the weak exception guarantee or higher, so you can be sure that everything is always in a valid state, though not necessarily a predictable state. The strong guarantee means that the data remains as if nothing even happened. I think this all applies to other languages with exception handling too, so you should know about it, maybe under a different name.

    I'm not aware that any language, C++ included, makes any such guarantee as a language-level feature. Depending on how well or poorly you do your exception handling, you can easily end up in any of those three situations, and which one you get can vary on a case-by-case basis.

    Actually you can avoid the static-vs-shared nightmare in C++ by just distributing source files and compiling all dependencies with your project at once.

    Sure, if you think the static-vs-shared nightmare is less of a mess than the "C++ takes forever to compile so why not give it even more stuff to compile?" nightmare. 🚎

    There are several open source libraries that support this consumption model, or you can do it manually, and it works reasonably well. That's basically what scripting languages do anyway, right?

    It's also what Delphi does, which builds exponentially faster than C++. On the other side, you have the JVM and the CLR, which avoid the "static vs. dynamic nightmare" by simply making everything dynamically linked. :P

    Last time I checked into it it looked like the Scala of C#, has that changed?

    I don't have enough knowledge of Scala to answer that in any depth, but from what I know of the language, I don't think they're very comparable.


  • BINNED

    @masonwheeler You can wrap everything in try blocks in C++ but you surely don't want to. For all the horrible flaws C++ has, of which afaict there are countably infinite, if you think try blocks are better than RAII I can't take you seriously. Which is quite curious since you obviously have a firm grasp of programming languages.


  • Impossible Mission - B

    @topspin said in C++ Stockholm Syndrome:

    You can wrap everything in try blocks in C++ but you surely don't want to.

    No, I don't want to "wrap everything in try blocks." I want the ability to express a try/finally scenario that does not involve resource management as a try/finally scenario that does not involve resource management.


  • BINNED

    @masonwheeler Then use a try block, or a scope guard?
    Got a specific example where this is horrible so we can make sure to talk about the same thing?



  • @masonwheeler said in C++ Stockholm Syndrome:

    "Zero cost" is a very deceptive term for what you linked to.

    Right, I can agree with you there - I didn't coin the terminology, I'm just repeating what it says there. It's only zero-cost for the non-exceptional path. The added binary size is honestly not even a concern for me.

    @masonwheeler said in C++ Stockholm Syndrome:

    Yeah... no. What the "zero-cost" part actually means is that you can add exception handling to your code with zero overhead on the happy path. The actual exception-handling part (ie the "taking care of what to do when an error occurs" side of things) actually becomes more expensive, with the rationale being that you're supposed to be spending the vast majority of your time on the happy path anyway, so this is an acceptable tradeoff.

    So then we are on the same page and fully agree here, sorry for the misunderstanding.

    @masonwheeler said in C++ Stockholm Syndrome:

    For all of the same reasons it's useful in a language without RAII. The entire RAII mechanism is built on try-finally semantics, but the C++ language doesn't expose a try/finally mechanism to C++ developers, which forces you to implement try/finally scenarios that aren't related to memory/resource management in terms of RAII. (Which is the classic definition of an abstraction inversion.)

    Could you provide an example where finally is useful? Usually all I need is the occasional catch(...) and throw; at the most. Perhaps I'm just yet to run into or think of a situation where that's insufficient.

    @masonwheeler said in C++ Stockholm Syndrome:

    Not really. As has been pointed out plenty of times before in articles on the subject, RAII is a really bad name for describing what's going on, because it's really not about acquisition at all, but about getting release and cleanup right.

    When I say RAII I really do mean that initializing something and acquiring a resource are fundamentally the same to me - if a failure occurs, you throw an exception from the constructor. It just so happens that C++ uses RAII for object scope.

    @masonwheeler said in C++ Stockholm Syndrome:

    I'm not aware that any language, C++ included, makes any such guarantee as a language-level feature. Depending on how well or poorly you do your exception handling, you can easily end up in any of those three situations, and which one you get can vary on a case-by-case basis.

    It's not a language-level feature, it's a library-level feature. My wishlist is for it to be language-level. See also: http://www.gotw.ca/gotw/082.htm

    @masonwheeler said in C++ Stockholm Syndrome:

    "C++ takes forever to compile so why not give it even more stuff to compile?" nightmare

    You only have to compile the object files once for any given configuration, but yes I do agree that first-time compile times are pretty long when using this method.

    @masonwheeler said in C++ Stockholm Syndrome:

    On the other side, you have the JVM and the CLR, which avoid the "static vs. dynamic nightmare" by simply making everything dynamically linked.

    And that's why their generics suck, because you can't instantiate templates without the source code. Dynamic linking is otherwise pretty great when you don't need to use generics.


  • Impossible Mission - B

    @topspin said in C++ Stockholm Syndrome:

    Got a specific example where this is horrible so we can make sure to talk about the same thing?

    You run into it all the time in GUI programming. Stuff that looks like:

    MyWidget.Frob();
    try:
       DoStuffWith(MyWidget);
    finally:
       MyWidget.UnFrob();
    

    Where Frob represents temporarily placing it in a specific state that should be reversed once you're done with whatever you're doing.

    That has nothing to do with resource acquisition or destruction, but you still want it to be exception-safe so that if a recoverable exception gets thrown, it doesn't leave your UI in an inconsistent state.



  • @masonwheeler said in C++ Stockholm Syndrome:

    You run into it all the time in GUI programming. Stuff that looks like:

    That looks like an anti-pattern to me. What's the rationale for that design?


  • Impossible Mission - B

    @lb_ said in C++ Stockholm Syndrome:

    And that's why their generics suck, because you can't instantiate templates without the source code.

    ...huh?

    It's perfectly possible to specialize generics without the source code. But you said "templates," which have nothing to do with C# generics and their suckiness or the lack thereof. In fact, what you're describing has everything to do with the suckiness of C++'s template system: it's all ephemeral and exists solely inside of the C++ compiler. In a language with good metaprogramming (again, this is coming from experience working with Boo) your metaprogramming constructs are part of the compiled output and can be used from code in another compile unit that references it.

    Dynamic linking is otherwise pretty great when you don't need to use generics.

    So now we're back to generics. Are you talking about generics, or are you talking about template metaprogramming? I'm getting confused here.


  • Impossible Mission - B

    @lb_ said in C++ Stockholm Syndrome:

    That looks like an anti-pattern to me. What's the rationale for that design?

    Just off the top of my head, disabling updates before loading a large batch of data, so you don't end up firing off a zillion event handlers or similar. There are plenty of other valid use cases, but that's the really big one in GUI work.


  • BINNED

    @masonwheeler Since you included the very first line, that does look like some form of resource acquisition, if you apply the term broadly. You're acquiring the "frobbed" state.

    A scope guard would let you express just that. Granted, yours definitely has a nicer syntax.



  • @masonwheeler said in C++ Stockholm Syndrome:

    Are you talking about generics, or are you talking about template metaprogramming? I'm getting confused here.

    I'm talking about both, because templates are used for both generics and metaprogramming at the same time. Writing generic code often means adding the occasional special case for a subset of types (e.g. treating types that fit a certain category differently). With templates in C++ you can do this with minimal code duplication using SFINAE and if constexpr. With generics in other languages, the code has to work the same and treat all types the same because it can't know anything about them in advance without asking at runtime. I think it's good to have both of these options in the same language, but the Java kind are less powerful as they can be used in fewer situations.

    @masonwheeler said in C++ Stockholm Syndrome:

    In a language with good metaprogramming (again, this is coming from experience working with Boo) your metaprogramming constructs are part of the compiled output and can be used from code in another compile unit that references it.

    Fascinating, how did they pull that off without runtime overhead? Wouldn't it have to compile the code differently depending on the never-before-seen types it is given? You've really got me curious about Boo now.



  • @masonwheeler said in C++ Stockholm Syndrome:

    Just off the top of my head, disabling updates before loading a large batch of data, so you don't end up firing off a zillion event handlers or similar.

    A simple lambda-based scope guard would be enough, but ultimately you're combating the awkward design of whatever framework you're using. I'll just assume it's a worthy tradeoff.


  • Considered Harmful

    @lb_ said in C++ Stockholm Syndrome:

    enforced static typing (if typoInVairableName = 1 is not an error, get lost)
    scope-based memory and resource management like RAII (gc is off by default)
    const-correctness and immutable by default (e.g. mutable keyword)
    exception handling (zero-cost, of course)
    ability to annotate strength of exception guarantees (nice to have at language-level, though I guess documentation is an okay fallback)
    as much zero-cost abstraction as possible (if it can be done at compile time instead of at runtime, it should be)
    as much stuff checked by the compiler at compile time as possible (if it can be a compile error instead of a runtime error, it should be)
    sane and consistent compiling/linking regardless of platform (dear god none of this shared-vs-static nonsense)
    powerful generics and metaprogramming (I'm talking passing around and returning types as variables and first class citizens)
    modern clean syntax (duh)
    good standard library (duh)
    UTF8 strings (duh)

    Dart seems to fit all the above.
    edit: not the GC one.


  • Impossible Mission - B

    @lb_ said in C++ Stockholm Syndrome:

    @masonwheeler said in C++ Stockholm Syndrome:

    Are you talking about generics, or are you talking about template metaprogramming? I'm getting confused here.

    I'm talking about both, because templates are used for both generics and metaprogramming at the same time. Writing generic code often means adding the occasional special case for a subset of types (e.g. treating types that fit a certain category differently). With templates in C++ you can do this with minimal code duplication using SFINAE and if constexpr. With generics in other languages, the code has to work the same and treat all types the same because it can't know anything about them in advance without asking at runtime.

    Agreed, this is annoying. It would work a lot better if the constraints were more expressive, and the C# language folks are actively looking at a way to do that. Their favored solution is an ugly hack that doesn't actually make anything better, and I'm trying to push them to do it a different way that would be much better, but requires an upgrade to the CLR metadata in order to make it work. We'll see how it turns out.

    Fascinating, how did they pull that off without runtime overhead?

    The same way C++ does it: by moving it all to compile-time overhead.

    In C++ the template system can be thought of as conceptually a preprocessor that does pattern matching and search-and-replace. There are a lot more details to it than that, but in terms of the input-to-output workflow, it's a black box that preprocesses your code by replacing templates with expanded templates. It's macro's big brother, and it's completely ephemeral, having no existence outside of the compiler itself.

    In Boo, a macro definition (or a meta method or an AST attribute--different metaprogramming constructs for different use cases) is ordinary Boo code that gets compiled and emitted in the output as ordinary classes and methods. Then when you use it, you reference the assembly containing the metaprogramming code, and the compiler loads it and executes it. Metaprogramming is just code that operates on the compiler's ASTs rather than on runtime data. (As a side benefit, this also means that you can attach the debugger to the compiler and set a breakpoint inside your metaprogramming code and see exactly what's going on, a thing that's simply not possible in C++.)

    Wouldn't it have to compile the code differently depending on the never-before-seen types it is given?

    Because it's ordinary Boo code, it can contain arbitrary conditional logic, including reflecting on the type and its members, if that's what you want to do.

    You've really got me curious about Boo now.

    :D



  • @pie_flavor

    Everything you can place in a variable is an object, and every object is an instance of a class. Even numbers, functions, and null are objects. All objects inherit from the Object class.

    That's not a good sign.

    If an identifier starts with an underscore (_), it’s private to its library

    Oh dear...

    int lineCount;
    assert(lineCount == null);
    

    :wtf: that should be a compile error...


    What is their memory model? Can variables be placed on the stack, or is it all heap-allocated?


  • Considered Harmful

    @lb_ said in C++ Stockholm Syndrome:

    I'm talking about both, because templates are used for both generics and metaprogramming at the same time. Writing generic code often means adding the occasional special case for a subset of types (e.g. treating types that fit a certain category differently). With templates in C++ you can do this with minimal code duplication using SFINAE and if constexpr. With generics in other languages, the code has to work the same and treat all types the same because it can't know anything about them in advance without asking at runtime. I think it's good to have both of these options in the same language, but the Java kind are less powerful as they can be used in fewer situations.

    Absolutely false. C++ implemented generics with templates because C++ is a shitty language. In actually good languages, such as C#, new versions of generic functions can be instantiated at runtime, even from DLLs. You can even do it via reflection. And in Rust the generics are done at compile-time, and I believe that they can be done across dynamic linking - certainly across static linking, which is how almost everything gets built, and yet they're still a language element, not a form of precompilation. Just because Java did something one way doesn't mean everyone else has to.



  • @pie_flavor Got an example of if constexpr but for C#? If it involves any runtime overhead, you can't say "absolutely false".


  • Considered Harmful

    @lb_ I have no idea what if constexpr means.



  • @masonwheeler said in C++ Stockholm Syndrome:

    I don't have enough knowledge of Scala to answer that in any depth, but from what I know of the language, I don't think they're very comparable.

    I forgot to respond to this, sorry. There's a joke that any problem you have with Java is "fixed in Scala". Scala and Java both, to my knowledge, compile to JVM bytecode. From what I'm reading about Boo, it's indeed in a similar camp: it wants to be better than C# but still use MSIL. That means every problem I have with .NET/Mono/MSIL/etc are problems I have with C# and Boo at the same time. Though if I can get past that then I would definitely try to first use Boo over C#. Still need to read more about it first.


  • Considered Harmful

    @lb_ JVM bytecode is just enough to implement Java in. CLR bytecode is extremely detailed. There's a hell of a lot you can do with CLR languages that you can't do in C#.



  • @pie_flavor said in C++ Stockholm Syndrome:

    @lb_ I have no idea what if constexpr means.

    Here's a trivial example:

    template<typename Iterator>
    auto some_generic_func(Iterator begin, Iterator end)
    {
        if constexpr(std::is_same_v<typename std::iterator_traits<Iterator>::iterator_category, std::random_access_iterator_tag>)
        {
            //algorithm optimized for random-access iterators
        }
        else if constexpr(true)
        {
            //generic algorithm (slower, but works in all cases)
        }
    }
    

    In the above, the code path to use is determined at compile time. There's no runtime checking to see if the iterators are random-access or not.

    @pie_flavor said in C++ Stockholm Syndrome:

    There's a hell of a lot you can do with CLR languages that you can't do in C#.

    Ah, I didn't know that - thanks!



  • @masonwheeler said in C++ Stockholm Syndrome:

    @topspin said in C++ Stockholm Syndrome:

    Got a specific example where this is horrible so we can make sure to talk about the same thing?

    You run into it all the time in GUI programming. Stuff that looks like:

    MyWidget.Frob();
    try:
       DoStuffWith(MyWidget);
    finally:
       MyWidget.UnFrob();
    

    Where Frob represents temporarily placing it in a specific state that should be reversed once you're done with whatever you're doing.

    That has nothing to do with resource acquisition or destruction, but you still want it to be exception-safe so that if a recoverable exception gets thrown, it doesn't leave your UI in an inconsistent state.

    std::frob frobbed(MyWidget);
    DoStuffWith(MyWidget);
    // compiler calls frobbed.~frob() when scope ends.
    

  • Considered Harmful

    @lb_ Well, Rust doesn't have anything like that, but new features are added every so often and I wouldn't be surprised to see it eventually. Right now conditional compilation is limited basically to crate features and a few global flags like operating system or pointer width.


  • Impossible Mission - B

    @ben_lubar said in C++ Stockholm Syndrome:

    @masonwheeler said in C++ Stockholm Syndrome:

    @topspin said in C++ Stockholm Syndrome:

    Got a specific example where this is horrible so we can make sure to talk about the same thing?

    You run into it all the time in GUI programming. Stuff that looks like:

    MyWidget.Frob();
    try:
       DoStuffWith(MyWidget);
    finally:
       MyWidget.UnFrob();
    

    Where Frob represents temporarily placing it in a specific state that should be reversed once you're done with whatever you're doing.

    That has nothing to do with resource acquisition or destruction, but you still want it to be exception-safe so that if a recoverable exception gets thrown, it doesn't leave your UI in an inconsistent state.

    std::frob frobbed(MyWidget);
    DoStuffWith(MyWidget);
    // compiler calls frobbed.~frob() when scope ends.
    

    Exactly. When all you have is a hammerRAII, everything starts to look like a nailclass with its own constructor and destructor. Add that in to the example and it looks a lot less simple and clean.


  • Considered Harmful

    @pie_flavor said in C++ Stockholm Syndrome:

    @lb_ Well, Rust doesn't have anything like that, but new features are added every so often and I wouldn't be surprised to see it eventually. Right now conditional compilation is limited basically to crate features and a few global flags like operating system or pointer width.

    Actually, never mind, I think you can do exactly that.

    #![feature(specialization)]
    
    pub fn some_generic_func<T>(begin: T, end: T) where T: Iterator {
        begin.specialize(end);
    }
    
    trait Specialization {
        fn specialize(self, end: Self);
    }
    
    impl Specialization for DoubleEndedIterator {
        fn specialize(self, end: Self) {
            double_ended_algorithm(self, end);
        }
    }
    
    impl Specialization for Iterator {
        default fn specialize(self, end: Self) {
            regular_algorithm(self, end);
        }
    }
    


  • @masonwheeler said in C++ Stockholm Syndrome:

    @ben_lubar said in C++ Stockholm Syndrome:

    @masonwheeler said in C++ Stockholm Syndrome:

    @topspin said in C++ Stockholm Syndrome:

    Got a specific example where this is horrible so we can make sure to talk about the same thing?

    You run into it all the time in GUI programming. Stuff that looks like:

    MyWidget.Frob();
    try:
       DoStuffWith(MyWidget);
    finally:
       MyWidget.UnFrob();
    

    Where Frob represents temporarily placing it in a specific state that should be reversed once you're done with whatever you're doing.

    That has nothing to do with resource acquisition or destruction, but you still want it to be exception-safe so that if a recoverable exception gets thrown, it doesn't leave your UI in an inconsistent state.

    std::frob frobbed(MyWidget);
    DoStuffWith(MyWidget);
    // compiler calls frobbed.~frob() when scope ends.
    

    Exactly. When all you have is a hammerRAII, everything starts to look like a nailclass with its own constructor and destructor. Add that in to the example and it looks a lot less simple and clean.

    MyWidget.Frob()
    defer MyWidget.Unfrob()
    DoStuffWith(MyWidget)
    

  • Impossible Mission - B

    @ben_lubar Yeah, defer is one of the things I do like about Go.



  • @masonwheeler said in C++ Stockholm Syndrome:

    @ben_lubar Yeah, defer is one of the things I do like about Go.

    How about the other keyword that has similar syntax that also appears in your post?


  • Impossible Mission - B

    @ben_lubar huh? Not sure what you're referring to there.



  • @masonwheeler said in C++ Stockholm Syndrome:

    @ben_lubar huh? Not sure what you're referring to there.

    Why don't you go look up the keywords that can go in front of a function call in go...


  • Impossible Mission - B

    @ben_lubar :P

    Goroutines are actually kind of meh to me. They're supposed to be all amazing and powerful and stuff, but I haven't actually seen any real-world examples of anything you can do with them that you can't just as easily accomplish (ie. same effect accomplished in a slightly different way) in C# with a generator or an async method. And those don't deadlock on you if you so much as look at you funny, the way goroutines are infamous for doing...



  • @masonwheeler said in C++ Stockholm Syndrome:

    And those don't deadlock on you if you so much as look at you funny, the way goroutines are infamous for doing...

    What?

    I've never had goroutines deadlock, but I've [frequently] run into problems with C# async stuff deadlocking, especially when running as part of a web server.


Log in to reply