1. 19
  1.  

  2. 28

    Yyyyyyyep.

    Edit: To expand a little, now that I’ve had some coffee, let me repeat one of soc’s points a little more vigorously: C++ will not get better until they start taking things away as well as adding things. I still have vivid memories of reading through a C++ book in undergrad, back in 2004 or something, and thinking “ok, I can’t just entirely avoid pointers in favor of references… so what good are references besides making more ways of doing the same thing?”

    1. 10

      References serve an entirely different purpose than pointers. A pointer is a type that can be NULL, so it may represent an optional object, whereas references are (theoretically) never NULL and always valid. These are valuable safety features. However, you would find it extremely difficult to write C++ code that exclusively uses references and no pointers. So both have their place in the language, though I would replace almost all pointers with std::unique_ptr if C++11 is available.

      I agree with the sentiment that the relentless growth of C++ and the reluctance of the committee to deprecate features is the source of many problems. Even though I know and write C++ for many years, I still find C++ features that weren’t previously known to me on a daily (!) basis. In other words, for thousands of days, I am daily surprised by this language. The complexity is unfathomable.

      1. 5

        references are (theoretically) never NULL and always valid.

        They are never NULL and often invalid. Like when you saved a reference to something that either (a) went out of scope; (b) was the result of dereferencing a pointer to memory that was freed afterwards. You can also make a null reference if you try very hard, but that doesn’t happen in practice, whereas invalid references sure do.

        1. 3

          You can also make a null reference if you try very hard, but that doesn’t happen in practice

          That’s just not true.

          void foo(int& i);
          ...
          p = nullptr;
          ...
          foo(*p);
          

          is not at all hard, nor is it it even that uncommon.

          1. 3

            It has never shown up in my 4 years of commercial C++ experience + 5 years non-commercial before that.

            1. 1

              I’ve seen it in the wild; debugged a core dump that had it. The evil thing is that the program crashes at the point where the NULL-reference is used, not where NULL is dereferenced (in compiled assembly, dereferencing a pointer to store it in a reference is a no-op). These two points may be far apart.

            2. 1

              This is undefined behavior.

            3. 1

              They shouldn’t be invalid, but it’s not entirely enforced by the compiler. The idea is that by using references, a programmer signals to the reader of the code that the value is intended to be a non-NULL valid object, and if that is not the case, the error is at whatever line of code that creates this invalid reference, not the one that uses it. In the wild, even inexperienced C++ programmers rarely create invalid references simply because it is harder to do so, whereas they routinely create invalid pointers. In addition to that, invalid references are almost always a programmer error, but invalid pointers are a regular occurrence and often part of a design that allows and accounts for such states.

              1. 4

                The idea is that by using references, a programmer signals to the reader of the code that the value is intended to be a non-NULL valid object, and if that is not the case, the error is at whatever line of code that creates this invalid reference, not the one that uses it.

                Yes, nonetheless it happens. And it doesn’t matter that “somebody else” made the error. The error is still there and it’s your code that crashes. I feel much better using a language that doesn’t put me in such situations.

                In the wild, even inexperienced C++ programmers rarely create invalid references simply because it is harder to do so

                For some definition of “rarely”. I’ve often seen code that passed references to objects wrapped in std::shared_ptr (often to avoid ARC overhead) into function arguments where those functions captured those by reference in lambdas which were executed after the std::shared_ptr freed the memory. Similarly, this regardless of its type is in practice used like a reference. There’s a lot of code in the wild that does something like PostTask([this] { /* do smth */ });.

                At the end of the day it doesn’t matter that “the error is somewhere else”. If the language makes something remotely straightforward, I’m sure to have to deal with that in a shared codebase at one point no matter how few errors I make myself.

                1. 2

                  Yes, whatever the compiler doesn’t prohibit is likely to end up in the codebase. However, I still think that references are valuable and provide better safety than pointers. Obviously it would be better if making mistakes was impossible. I’m not whitewashing C++’s serious shortcomings here.

                  As for the discussion about invalid references, I’ll leave you with this short story: http://www.gotw.ca/conv/002.htm

            4. 3

              References serve an entirely different purpose than pointers.

              The official (D&E of C++) purpose of references is to enable operator overloading.

              1. 2
          2. 12

            C++ is just worse C. if I want C, I can use C. If I want a high level language, there are many good ones. C++ is neither and sits in a power spce I never want (big complex language with nothing but footguns)

            1. 17

              Yes.

              Some people may argue that “C++ replaced most of the dangerous techniques with much better ones in the recent years”, but that’s

              a) not true (it added more stuff, it didn’t remove the old ways), and

              b) missing the point (C++’ unlimited growth is one of the core criticisms of C++).


              The interesting take away from this article (and many similar ones), is that C++ has an extreme selection bias – it only seems to be able to attract people who are unable to see its problems.

              Many languages have something like this – in the end it’s often a matter of preference– but C++ seems to be the most extreme case of this.

              1. 9

                [C++] only seems to be able to attract people who are unable to see its problems.

                I love C++. Yet I wholeheartedly agree with your criticisms, and have many more myself (e.g. it’s still impossible to build a compile-time safe Option). I believe Rust is objectively superior in almost every way.

                But that doesn’t mean we should all ignore the great things done with C++, nor the value of lessons learned from its widespread use. I don’t think C++ is “plain bad” at all. It has its place for good reason. Soon Rust should replace it everywhere, also for good reason.

                1. 5

                  It always struck me as sort of a hoarder’s language, in that no idea, no matter how terrible, has ever been discarded, so the surface area of the language is huge, and this leads to innumerable mutually incompatible dialects.

                  1. 3

                    I currently use C++ because the specialized graphics libraries I need are only available in C++. I’m proficient in it because I was paid to write C++ code professionally for over 10 years. None of this proves that I am “unable to see its problems”, as @soc puts it. Yes, I know about Rust, but there’s the high learning curve, the low productivity, and the difficulty or impossibility of interfacing to C++ template libraries (depending on the library). Reimplementing the libraries I need to use in Rust is not an option. More generally, I think that C++‘s huge library ecosystem is a big factor in why people continue to use it. It’s not ignorance or blindness to the language’s problems.

                    I’ve looked at Zig and I like the fact that you can directly include a C header file. Makes it very easy to migrate from C to Zig. If someone created a decent language with the same level of integration with C++, I’d be very interested.

                    1. 1

                      a) not true (it added more stuff, it didn’t remove the old ways), and

                      This is equally true of other languages with an ISO standard.
                      Member countries vote on whether a standard should be accepted; standards that are too incompatible with existing code won’t be accepted.
                      The COBOL80 standard is an example of a standard that was rejected.
                      Another factor is that deprecating or removing functionality from a standard is often ineffective; if it’s widely used then compilers and runtime libraries will continue to support it.

                      1. 6

                        This is equally true of other languages with an ISO standard.

                        Yes, but this doesn’t detract anything from the problem statement.

                        Another factor is that deprecating or removing functionality from a standard is often ineffective; if it’s widely used then compilers and runtime libraries will continue to support it.

                        But that’s a problem C++ created itself: by basically never fixing, cleaning or removing any existing feature, the community only attracts people that don’t care about that.

                        Same why languages with bad documentation can’t seem to improve it – because they literally can’t attract people who care about good documentation.

                        One example of a large language which managed to do much better is C#.

                    2. 5

                      Yes.

                      For me a big issue is Unicode support. For example with a language like Go, you can do something like this:

                      package main
                      func main() {
                         println("😀")
                      }
                      

                      and it just works. Same goes for C#, D, Dart, Nim, PHP, Python, Ruby, Rust, and probably others. But with C++, you have to do this:

                      #include <codecvt>
                      #include <iostream>
                      int main() {
                         std::ios_base::sync_with_stdio(false);
                         std::locale utf8(std::locale(), new std::codecvt_utf8_utf16<wchar_t>);
                         std::wcout.imbue(utf8);
                         std::wcout << L"😀" << std::endl;
                      }
                      

                      Somehow C++ is 2 decades older than some of these languages, yet its a decade behind in Unicode support. That is not acceptable.

                      1. 2

                        But with C++, you have to …

                        Bull shit.

                        This works fine:

                        #include <cstdio>
                        int main(){
                        	puts("😀");
                        }
                        

                        as does loads of other things.

                        1. 1

                          Only if your platform defines C strings as UTF8. Windows doesn’t guarantee this, and, in fact, only added support for it recently. Neither does the Linux Standard Base, or POSIX, though some distributions have dropped support for non-UTF8 locales, so you can safely assume UTF8 if you’re on Ubuntu or something.

                          1. 2

                            I think this does work to the same extent it works in eg, Go and Rust? Just shoves utf8 bytes into fd 1. So, the upthread comparison does seem a bit unfair to C++ to me.

                              1. 1

                                Oh wow, totally didn’t expect that, thanks for the pointer!

                            1. 1

                              Prove it: Take a screenshot of an environment where golang produces the correct output and my C++ example doesn’t.

                              I run my programs on computers, and I think that code I posted will run correctly on anything you can buy in a store today, or your money back.

                                  1. 2

                                    I’d rather use wcout than mess with the registry. Messing with the registry hurts your application’s ability to coexist with other applications on the same machine, only works on Windows, and requires admin access.

                                    1. 1

                                      You can also do it from the Control Panel

                                      https://stackoverflow.com/questions/56419639

                                      1. 2

                                        That’s not better. I write stuff for Windows that needs to run on computers that other people own. I am not going to ask them to enable experimental features in the Control Panel.

                                  2. 0

                                    Are you aware rust and golang are different languages?

                              1. 1

                                You are right.

                                I am not sure what was giving me trouble previously. Maybe I was using an old compiler. I have tried many combinations try to get your example to break, but it does work. As long as I have at least Windows 10 1903, with at least Windows Terminal 0.3.2142. Then set Unicode:

                                Windows Registry Editor Version 5.00
                                [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Nls\CodePage]
                                "OEMCP"="65001"
                                

                                and restart. After that your code does work.

                            2. 4
                              1. Does the Internet ever like multiparadigm languages? I’m trying to think of one that it does. Even with something like OCaml, everyone quickly says, “and don’t use the OO part!”

                              2. Does the Internet ever look back fondly on older, dominant languages? There’s a narrative that the older languages must be defective because we’ve all moved on; this is a necessary belief for the orthodoxy of progress to be held.

                              These are meta questions that don’t apply to C++ as much as languages themselves.

                              1. 3

                                Does the Internet ever look back fondly on older, dominant languages?

                                I see love for plain C.

                                I don’t think you can write off C++ hate as hipsterism.

                                1. 3

                                  I will freely admit that I prefer opinionated languages over anything goes languages. Multiparadigm languages I think fall in the anything goes category. I find that the more paradigms I need to hold in my head the more likely I’m going to be frustrated when I’m debugging an issue in production concerning 5 year old code. I think this is because:

                                  • My actual on the job greenfield development experience is close to nil.
                                  • I spend 80% of my on the job time reading and detangling old code.

                                  As a result the less cognitive overhead a language forces onto me the better I like it. It’s the same reason why I prefer explicit to implicit code.

                                  1. 4

                                    I think the core issue is that multiparadigm languages are rarely orthogonal – instead of merging functionality from different paradigms (where it makes sense) they just have them living alongside each other.

                                    That’s why I believe that there is nothing inherently “larger” or “anything goes” about multiparadigm languages, it’s just many of the existing examples did a poor job.

                                    One trivial example is if-then-else statements, ternary expressions and pattern matching – there is no reason why a language needs to have all three! Just merge them until you have “one way of doing things”.

                                  2. 2

                                    I don’t know who counts as “the Internet,” but it’s definitely possible to appreciate older languages that are well-designed. For example, Ada is intriguing (I think it’s even had a tiny renaissance as people are revisiting) and, of course, Lisp lives on. Even languages like Fortran are, if not loved, then at least respected. C also isn’t hated, although experience has shown us that it has flaws (memory safety, undefined behavior, and so on.)

                                    As for the first question, it depends on what you mean by “multiparadigm.” Python is certainly a hybrid language. You could argue that a language like Rust is a multiparadigm language with procedural and functional aspects. In fact, you can use structs in Rust and Go in a way that approximates the hybrid approaches of Python.

                                    The issue with C++ (and some other multiparadigm languages) is that it feels like the implications of the combinations of features aren’t thought through (or, in some cases, even thought of).

                                  3. 3

                                    No. Sure, it’s popular to trash the language, but a lot of the criticisms directed at C++ apply primarily to C++98, and C++11 and later provide significant boons to clarity, terseness, and simplicity. The standards committee ensures compatibility with older code by not removing features, because there’s millions upon millions of lines of old code which people rely on. I’ve worked in several codebases with 6+ million lines alone, some dating back to the mid 90s.

                                    A lot of books and websites teach all sorts of features of C++ which are often not used in practice. Inheritance? Seldom used in code I deal with, and I think I’ve only seen one actual case of multiple inheritance which involved two non-abstract base classes. Exceptions? I don’t think I’ve written throw or try/catch in at least 5 years, and projects often disable them for performance. RTTI? Never touched it, and I usually disable it anyways. Keeping and calling a pointer to member function? Never used it, and lambdas and std::function cover most of the few cases where you’d actually want to do this. new/delete and raw pointers? Nearly all code you see uses std::unique_ptr with std::shared_ptr. Templates? Most programmers don’t write them, and constexpr covers some of their use cases for compile-time configuration and calculations.

                                    It’s a tool for writing performance-oriented code. When every allocated byte and every microsecond counts, I write C++. Outside of that, the effort for the product doesn’t make sense for the tool. For programs like that listed in the post, I’d probably drop back and punt to Python or bash.

                                    1. 2

                                      To rehash a classic,

                                      find . -iname '*.txt' -print |
                                      xargs cat |
                                      tr -cs A-Za-z '\n' |
                                      tr A-Z a-z |
                                      sort |
                                      uniq -c |
                                      sort -rn |
                                      head -n 10
                                      
                                      1. 5

                                        You’re making the same mistake Drang did. The point of the Programming Pearls column wasn’t to compare Literate Programming to shell scripting. Knuth was just giving an example of what literate programming looked like. The actual example Bentley gave him wasn’t the point, so talking about how “Unix Way is better” for that problem misses the point.

                                        Similarly, the point of this article isn’t that this problem is easy in C++. It’s that it’s clearer and safer in modern C++ than in traditional C++. That you can do it more cleanly in bash is besides the point.

                                      2. 1

                                        Yes and no. I would draw a line between the language and the ecosystem/tooling.

                                        C++17 is a better language than C++98. It’s much safer, more user friendly. But I don’t use it for my hobby projects, because some of its aspects are too old, and nobody is doing anything to fix that. I am using it in work though.

                                        Tooling is still in a sorry state. There’s still no package management, #include-based modules are still from the ’80s. Lots of new languages (even from the ‘90s) have these problems solved, but not C++ – I have an impression that authors are focusing on mostly irrelevant things (user defined literals? uh cool, but was it really necessary? or designated initializers – also fine, but that doesn’t change how C++ will be used much). Some features like concepts are pretty experimental, and I think C++ should be more conservative, it’s used in too many projects to get away with experimental stuff that gets introduced in one revision, only to be deprecated in another (and C++ had features like that). Some features instead of being designed are a result of some kind of emergent behavior (like SFINAE; at least it looks like that to me).

                                        By the way, don’t listen to people who say that C is better than C++ – very often they say that because they didn’t manage to punch through the C++ learning curve.

                                        1. 1

                                          There’s still no package management

                                          I still consider this a feature of C++. Language-based package management fundamentally assumes that a program is written in one language. That is rarely true. I recently had to install Jupyter, which depends on Python and NodeJS (and some other stuff, including a bunch of native C/C++ libraries). I had a version mismatch between one of the Python components installed via pip and the NodeJS component installed via npm. This was such a common occurrence that there was documentation on how to address it.

                                          On Windows, you can use NuGet for C++ packages, but NuGet is only really cross-platform for .NET packages. It’s probably a better approach overall than language-specific package managers.

                                          #include-based modules are still from the ’80s

                                          Modules are not #include-based, they’re pre-parsed ASTs that can be lazily instantiated. They’re new in C++20, and have been prototyped and the design reviewed in WG21 meetings repeatedly over the last decade, so I don’t think you can fault the standards committee for not working on them.

                                          Some features like concepts are pretty experimental

                                          Concepts fix a real problem in C++ (understanding why a template instantiation failed from 100 pages of compiler error). They were originally proposed for C++11 and the prototype has been refined considerably in last 10 years, I don’t think it’s fair to call them experimental: they’re the result of well over a decade of experimentation.

                                        2. 1

                                          I just don’t understand why the author uses regular expressions instead of delimiting by spaces like wc and others do.

                                          1. 2

                                            Makes it easier to exclude stuff like punctuation, and they can easily filter words by “two letters long or more”. As a bonus, while you’re at it, might as well use it to check if a file ends with .txt. They did claim performance was not a concern, and regex is a handy tool.

                                            But I don’t think there’s a reason to focus on this minor detail.

                                            1. 1

                                              I mentioned it because it’s built into the STL, one of the ever-growing parts of C++.

                                              1. 1

                                                My C++ knowledge is pretty low, so I made a guess based on the approach I would probably take on any so-called modern language :P