C++26: A Placeholder with No Name
89 points by jandeboevrie 3 days ago | 152 comments
  • SideburnsOfDoom 19 hours ago |
    See c#:

    "A discard communicates intent to the compiler and others that read your code: You intended to ignore it.

    You indicate that a variable is a discard by assigning it the underscore (_) as its name."

    https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals...

    2020: http://dontcodetired.com/blog/post/Variables-We-Dont-Need-No...

    • tempodox 11 hours ago |
      Yep, Microsoft took only almost 50 years to do what Prolog could do in 1971, and the C++ standards committee took ~5 years longer.
  • agumonkey 19 hours ago |
    Makes me wonder when '_' was first used as a token to denote unused information. Prolog ? ML ?
    • nicoburns 18 hours ago |
      I think this predates computers entirely.
      • SideburnsOfDoom 18 hours ago |
        I think so, yes. English novels written in the 1800s would reference "Mr A------ from ------shire". As if they were redacting personally identifying information. (1)

        Underscores and dashes are not that different - especially as this is not just pre-computer but pre-typewriter.

        And now, underscores are a logical choice when the dash is already in use as a minus sign.

        1) https://literature.stackexchange.com/questions/1944/why-are-...

        https://forums.welltrainedmind.com/topic/141704-jane-austens...

        • adrian_b 14 hours ago |
          Underscore has been used in programming for the first time by the language IBM PL/I, in 1964-12, where it replaced the hyphen/minus character that was used by COBOL to make more readable the long identifiers.

          Replacing hyphen/minus with underscore has been done precisely for removing the ambiguity with the minus operator (In COBOL the ambiguity was less annoying, because the arithmetic operations were usually written with words, e.g. ADD, SUBTRACT, MULTIPLY and so on).

      • agumonkey 18 hours ago |
        Good point, this is clearly something that could go back centuries.. if not all the way back to clay tablets.
    • lieks 18 hours ago |
      If we're counting only programming languages (and not fill-out forms), Prolog had it in 1971, before ML. ASCII didn't include _ until its 1963 draft, so it's probably somewhere in that time.

      I'll guess it's probably Prolog, but maybe Planner (Prolog's predecessor) had it too.

  • kookamamie 18 hours ago |
    What a convoluted mess.
    • asah 17 hours ago |
      My thought exactly. But that's C++, a bolted-on mess of crap whose redeeming features are (a) roughly the same execution performance as C and assembly, and (b) it's safer and higher productivity than C or assembly.

      At this point, it feels like a matter of time before Rust replaces C/C++.

      • dgfitz 17 hours ago |
        There are decades and decades of code written in cobol that run the modern banking system.

        Multiply that code base size by like, 78.3, and you’re possibly in the same galaxy as the all the c++ codebases out there that will be maintained for the next 50 years.

        Rust may eat the lunch of c++ moving forward, the language will never go away.

        • larodi 15 hours ago |
          Abso-fkn-lutely! Stable computing protocols of all sorts don’t go away overnight and that is something everyone in IT should absolutely get used to. I expect C/C++ to live long beyond 2050.
        • DowsingSpoon 12 hours ago |
          >the language will never go away

          Just like COBOL! Seriously, _just like COBOL_. The language will fade in importance over time. C++ will be relegated to increasingly narrow niches. It will eventually only be found in "legacy" systems that no one wants to spend to rewrite. No one young will bother at all to learn C++ at all. Then, in a few decades, they'll be pulling folks like you and me out of retirement to maintain old systems for big bucks.

          • timewizard 10 hours ago |
            I doubt this analysis. The base of computing where C++ is used is exponentially larger than the base where COBOL was used. In particular the compilers we currently use are written in it.
      • akkad33 17 hours ago |
        C++ is safer than C? How?
        • JTyQZSnP3cQGa8B 16 hours ago |
          You can write whole applications that compiles to the same assembly code without using any kind of memory management thanks to the destructors, smart pointers, and all the objects of the STL.
          • flohofwoe 16 hours ago |
            This just pushes the problem into the C++ stdlib, which has plenty of memory-corruption issues too but just calls it 'undefined behaviour' (see things like dangling std::string_view, missing range checks in containers or iterator invalidation). In C you avoid those issues by reducing dynamic memory allocations to a minimum, prefering value types over reference types and using function APIs as 'safety boundaries' (instead of directly poking around in random data). Different approach, but not any less safe than C++.
            • vlovich123 15 hours ago |
              > In C you avoid those issues by reducing dynamic memory allocations to a minimum, prefering value types over reference types and using function APIs as 'safety boundaries'

              "In Rust that's just pushing the problem to the borrow checker and codegen which has plenty of memory corruption issues too but just calls it "bugs". In C++ you avoid those issues by reducing dynamic memory allocations to a minimum, and using checked APIs as 'safety boundaries' instead of directly poking around random arrays. Different approach but not any less safe than C++".

              Both statements are pretty ridiculous. It's pretty clear that moving up in terms of the safe abstractions that are possible makes your code safer because the safety is there when you reach for that kind of programming paradigm & the programming paradigms you need are typically a property of the problem domain rather than the language (it's it's the intersection of "how is this problem solved" and "what tools does the language give you"). In C it gives you few tools and you either twist into a pretzel trying to stick to that or you write our own tools from scratch every time and make the same but different mistakes over and over. No language is perfect and all will guide you to different takes on the same problem that better suit to its paradigm, but there are intractable parts that will force you to do things (e.g. heap allocation and more involved ownership semantics are not uncommon). Moreover C very limited ability to manage your own codebase to define API boundaries - the only tool is opaque types with a defined set of functions declared in 2 different files.

              • uecker 7 hours ago |
                The opaque types in C are great though. And because no info leaks via header, you have very fast compilation. The y"ou write from scratch every time" is a weird statement. I do not delete my own code after each project and there exist plenty of libraries.
        • UebVar 16 hours ago |
          Somewhat separating owning and non owning memory in the type system goes a long way. Also a much better standard library and a stricter typing discipline.

          The fact that it's mostly backwards compatible means you can reproduce almost all issues of c in c++ awell, but the average case fares much better. Real world C++ does not have double-frees, for example. (As real world C++ does not have free() calls).

      • flohofwoe 17 hours ago |
        Tbf, Rust is catching up fast to become the same mess (especially in the stdlib).

        > it's safer and higher productivity than C or assembly

        Debatable - in C and even more so in assembly, the unsafety is at least directly in your face, while in C++ it's cleverly diguised under C++ stdlib interfaces (like dangling std::string_views, all the iterator invalidation scenarios etc... meaning that memory corruption bugs may be much harder to investigate in C++ than in C (or even assembly) - which in turn affects productivty (e.g. what's the point in writing higher level buggy code faster when debugging takes much longer).

        > it feels like a matter of time before Rust replaces C/C++

        It may replace C++, but C will most likely outlive at least C++, and I wouldn't be surprised if Rust too - even if just as C API wrappers to allow code written in different modern languages to talk to each other ;)

        • kibwen 16 hours ago |
          > Tbf, Rust is catching up fast to become the same mess (especially in the stdlib).

          I've never seen any users who avoid using Rust's stdlib on principle. The closest thing is the use of the parking_lot crate over the standard Mutex types, but that's getting better over time, not worse, as the stdlib moves closer to parking_lot in both implementation and API. Other than that, there's only been one "wrong" API that needed to be deprecated with a vengeance (mem::uninitialized, replaced by mem::MaybeUninit). Especially compared to C++, the fact that Rust doesn't have a frozen ABI means it's free to improve the stdlib in ways that C++ can only dream of. While I do wish that the Rust stdlib contained some more things (e.g. a standard interface to system entropy, rather than making me pull in the getrandom crate), for what it provides the Rust stdlib is quite good.

          • vlovich123 15 hours ago |
            Ironically c++ as well refused to define an ABI. That’s not why the language is bad. It’s the lack of editorial tools within the language the standard body has so it has no evolution path and thus ossifies under its own inertia. It’s amazing they still haven’t done anything about it.
          • accelbred 9 hours ago |
            I avoid the Rust stdlib. The stdlib uses panics and dynamic memory allocation, neither of which are great for embedded usecases or writing software adhering to safety-critical coding practices. no_std is common for embedded targets, and IIRC the linux kernel couldn't use stdlib because of panics.
        • vlovich123 15 hours ago |
          > Rust is catching up fast to become the same mess (especially in the stdlib)

          Care to provide examples?

          I think they generally do a very good job at curating the APIs to feel very consistent. And with editions, they can fix almost any mistakes they make whereas c++ doesn’t have that. In fact, the situation in c++ is so bad that they can’t fix any number of issues in basic containers and refuse to introduce updated fixes because “it’s confusing”. The things they keep adding to the stdlib aren’t even core useful things that come out of the box. Like missing a networking API in 2025. The reason is they have to get it perfect because otherwise they can’t fix issues and they can’t get it perfect because networking is a continuously moving target. Indeed we managed to get a new tcp standard before c++ even though it had even worse ossification issues. Or not caring about the ecosystem enough to define a single dependency management system because the vendors can’t get agreement or a usable module system. Or macros being a hot mess in c++ some 20 years after it was already known to really suck.

          Now it’s possible given enough time rust will acquire inconsistent warts over time similar to c++, but I just don’t think it’ll ever be as bad in the standard library due to editions and automated migration tools being easier against rust. Similarly, I think editions give them the flexibility to address even many language level issues. Inertia behind ways of doing things are the harder challenges that are shared between languages (eg adoption of Unsend might prevent the simpler Move trait from being explored and migrated to), but c++ is getting buried under its own weight because it’s not a maintainable language in terms of the standard because the stewards refuse to realize they don’t have the necessary editorial tools and keep not building themselves such tools (even namespace versions ended up being aborted and largely unused).

      • poincaredisk 15 hours ago |
        Rust is a C++ replacement, but not a C replacement, for many C use cases. Language that may replace C is Zig.
        • pwdisswordfishz 15 hours ago |
          What are those use cases?
      • readyplayernull 15 hours ago |
        I find the C vs C++ battle amusing after having lived in the 90s and the Pascal/Delphi vs C/C++ holy wars.
      • justin66 13 hours ago |
        > At this point, it feels like a matter of time before Rust replaces C/C++.

        I expect Rust's successor might have a shot at replacing C++.

        By the time C++ was as old as Rust, it had conquered the world. If Rust coulda, it woulda.

        • caspper69 5 hours ago |
          I think you're right.

          As soon as you start writing big(ger) software in Rust, its lacking ergonomics really become apparent.

          .as_mut().unwrap().unwrap().as_mut().do_stuff() gets really old after a while.

          And I am not convinced that borrow checking is the panacea that it's made out to be. Region-based memory management can accomplish the same safety goals (provided there's a sane concurrency model), without forcing people into manually opting into boxing and/or reference counting on a per-reference basis.

          Throw into that the pain of manual lifetime management (it's not always elided, and when it needs to change, it's painful), I honestly believe it's far more reasonable to ask programmers to throw shit into two or three arenas and then go hog-wild with references than it is to expect them to deal with the tediousness of the way Rust does things.

          We are just cargo-culters (no pun intended).

    • jayd16 12 hours ago |
      Seems pretty clean to me? Do you mean the current state of affairs is a mess?
    • devnullbrain 11 hours ago |
      I don't think the one character solution introduced at the bottom of TFA is convoluted. Could you explain why you think the solution - introduced at the bottom of TFA - is convoluted?
  • wild_pointer 18 hours ago |
    "In addition, there are some variables such as locks and scope_guards that are only used for their side effects"

    ...

    "This solution is also similar to other languages’ features or conventions"

    As far as I know, in Rust you can't use "_" for that, as the value will be dropped right away, so the mutex/resource/etc. won't live for the scope.

    • awestroke 17 hours ago |
      No, it lives until the end of its last scope regardless of name
      • tialaramex 17 hours ago |
        This is not a name, it's the specific choice not to assign it to any name, and so your parent was correct that it's dropped immediately.

        https://rust.godbolt.org/z/P1z7YeP4Y

        As you see, Rust specifically rejects this code because it's never what you meant, either write explicitly "I want to take and then immediately give away the lock" via drop(LOCK.lock()) (this really might be what you wanted, the Linux kernel does this in a few places) or write an actual named placeholder variable like in my third example function.

        • SideburnsOfDoom 17 hours ago |
          > This is not a name, it's the specific choice not to assign it to any name

          Yeah, it's the same in c#. This is noticeable when in the same scope you can have multlple "_" vars. If these were actual names, they would be a name clash.

          One of the uses is take some parts of a tuple return and ignore the others.

          e.g.

          var (thingIwant, _, _, _) = FunctionThaReturnsATupleOfFourThings();

          There are three "_" in that expression, and they are different vars with different types. They don't really have the same name, they don't have names at all.

        • vlovich123 15 hours ago |
          But on the other hand the motivating problems noted in C++ don't exist. It is 100% legal to completely rebind a variable in the same scope, it never warns that `_` variables are unused nor about unused `_` prefixed variables. I think `_` being immediately dropped is maybe one of those unfortunate decisions we'll look back in 20 years and regret.
          • tempodox 10 hours ago |
            Ever heard of `-Wunused-variable`?

            > rebind a variable in the same scope

            But only re-assigning values of the same type. Otherwise:

              int x = foo();
              int x = bar();
              error: redefinition of 'x'
            and,

              int x = foo();
              long x = bar();
              error: redefinition of 'x'
            
            What are you talking about?
  • wiseowise 17 hours ago |
    Not every "problem" needs a solution.

    At this point only LLMs will be able to decipher every intricacy of C++.

    • lionkor 16 hours ago |
      No, they cannot, they are word prediction machines.
    • devnullbrain 11 hours ago |
      I swear some of you read 'C++' in the submission title and immediately assume it's tagging on inscrutable, additional ways of doing things. This is making C++ code _less_ intricate.
    • tempodox 11 hours ago |
      What a wise display of complete ignorance.
  • ulbu 17 hours ago |
  • carom 17 hours ago |
    I am so shocked at how many people use `auto` in C++. I can not think of a worse thing to do to your code in terms of readability and future maintainability. Maybe it is OK if you use an IDE to identify types for you but I still hate it. I am trying to learn a new library right now with light documentation which means reading the code, and between typedefs, auto, and technical debt, it is a tedious exercise to figure out something's type, to go look up its function, to see what type that is returning.
    • JTyQZSnP3cQGa8B 16 hours ago |
      The auto keyword should not go in a public API, but internally it's very useful especially when you create objects like `auto ob = make_unique<VeryLongClassName>(...);` or any other kind of function call where the type is obvious and would be identical on both sides of an assignment.

      As for your particular issue, using an IDE is essential, and the typedef keyword is almost obsolete, so I guess you stumbled upon a strange project. I would be curious to know what it is if it's open-source.

      • carom 16 hours ago |
        It is Slang. A very cool project and it only got its public release relatively recently, so some sins are forgiven but there are so many typedefs.

            typedef struct ShaderReflection ProgramLayout;
            typedef enum SlangReflectionGenericArgType GenericArgType;
        
        https://github.com/shader-slang/slang/blob/master/include/sl...
        • JTyQZSnP3cQGa8B 16 hours ago |
          It looks like C++98 to me: no `pragma once`, `typedef uint32_t SlangUInt32` seems strange, `typedef bool SlangBool` is definitely useless. The auto keyword is the least of my problems here.

          > years of collaboration between researchers at NVIDIA, Carnegie Mellon University, Stanford, MIT, UCSD and the University of Washington

          Now I understand why, it's the kind of project that you can't upgrade easily.

          • flohofwoe 16 hours ago |
            `#pragma once` is not a panacea, it can still lead to double inclusion in scenarios where the same include file is accessible under different path names (granted, that's a very esoteric scenario). Besides, `#pragma once` is neither part of the C nor C++ standard. It's just a common convention between compiler vendors - so technically any code that uses pragme once as the only include guard is not standard C or standard C++ (but tbf, hardly any real-world code is fully standard compliant).

            Typedef'ing common types to your own type names is absolutely fine as long as it is unlikely to collide with the typedefs of other libraries in the same project.

            • vitus 16 hours ago |
              > Typedef'ing common types to your own type names is absolutely fine as long as it is unlikely to collide with the typedefs of other libraries in the same project.

              I read the parent post as indicating that "this is C++; we spell this as `using Foo = Bar;` now." Type aliases (or namespace aliases, or using-declarations) are not dead, but the typedef keyword in C++ is largely only retained for backwards compatibility.

              The core issue here is that type aliases add a layer of indirection. That can be useful if the user shouldn't need to know the implementation details and can interact with said type purely through the API -- do I care if a returned Handle is a pointer, an int, or some weird custom class? Everyone is used to file descriptors being ints, but you aren't going to do math on them.

            • elteto 14 hours ago |
              #pragma once is a de-facto standard now, and if it’s not in the actual standard then that says more about the failings of the standard writers than anything else.

              And there’s absolutely no reason to typedef _standard_ int types anymore. Not in C, and definitely not in C++. That’s just crusty old practices. Maybe if you want to have nice short types like u8, i8, etc, I can understand that. But SlangUint32 is just ugly.

              • bluGill 10 hours ago |
                Pragma once isn't in the standard because there are cases where it doesn't work and to standardize it means making the standard significantly longer to catorgize when the compiler is allowed to not work. I've been in discussions and they all conclude it is not worth the effort
            • maccard 12 hours ago |
              Pragma once is widely enough accepted that anyone who argues against its use for that purpose better either be able to tell me the compilers it doesn’t work on, or I’m going to assume they’re being pedantic for the hell of it.

              And honestly, anyone who relies on the scenarios Pragma once fails in (Compiling off network shared and symlinks) should really fix those monstrous issues instead. The places Pragma once trips up in are likely to be issues for detecting modifications in incremental builds anyway.

              • throw16180339 11 hours ago |
                There used to be a couple holdouts, but all major C++ compilers have supported it for years (https://en.wikipedia.org/wiki/Pragma_once).
              • bluGill 10 hours ago |
                Prarma once works in the vast majority of cases. However there are some rare ones where it fails. Every attempt to fix those last ones breaks some other rare case or profilings shows significant compile time increases.

                use pragma once where it works in internal code but never in headers you ship to someone else is a simple rule that should work well enough.

                • maccard 9 hours ago |
                  > However there are some rare ones where it fails.

                  As I said in my previous comment, those cases are very very often cases where the compialtion model is broken, and it's held together by luck.

                  > Every attempt to fix those last ones breaks some other rare case

                  My experience (and I have experiences of this) have been that the cases where pragma once fails, other tools (source control, built tools) cause "random" problems that usually are hand waved away with "oh that problem, just run this other makefile target that someone has put together to clear that file and rebuild".

                  > or profilings shows significant compile time increases.

                  Again, my experience here has been that the compile time change is usually a result of a broken project model, and that there are other gotchas like "oh you can only include files a.h and c.h but if you include b.h then you'll break the internal build tool". Also, that taking the hit to make your build correct unlocks optimisations that weren't possible before.

                  Also, the projects that have these kinds of warts are usually allergic to doing anything new whatsoever, making any changes or improvements, upgrading compilers and libraries. I suspect using C++17 is enough to scare them off in many cases.

                  If it's good enough for QT or LLVM, it's good enough for me.

                  • bluGill 5 hours ago |
                    It isn't luck - I know exactly what I did to my package manage to get that file installed into two different locatian. I know exactly which -I options I passed to the compiler causes that one file to be found in different locations depending on quotes or brackets to be used. And it works great if I don't use pragma.

                    i also know why I had to do that aweful abuse to what anyone sane would call wholesome. I don't like it but there are other things going on and I don't want to talk about it anymore.

            • ogoffart 11 hours ago |
              I've been using #pragma once in my C++ libraries for a decade and nobody ever reported any problem with that. I did get bug report about using perfectly standard C++ features that were not properly implemented in some compilers.

              > the same include file is accessible under different path names (granted, that's a very esoteric scenario)

              And most likely a build system problem anyway if, say, different versions of the same library get included in your build.

              • bluGill 10 hours ago |
                I use it and ran into issuses when I put a file into two different locations using my package manager. The reason I wanted a file in two different locations is something I don't want to talk about.
      • cjfd 14 hours ago |
        I agree that the auto keyword should be used sparingly. Things you mention like the output of make_unique and make_shared are a good exception since it is very clear what the resulting type is. Also, one might use auto to store a lambda in a local variable because it does not have a type that you can type.
    • Too 16 hours ago |
      Every other language does that by default with "var", "let", or in some languages nothing at all. Within functions it doesn't matter that much and using an IDE takes care of quick lookups anyway.
      • dgfitz 12 hours ago |
        Wasn’t typescript created to fix this problem for JS?
        • jeremyjh 10 hours ago |
          Using `let` does not make the expression un-typed in Typescript. It means the type is inferred, and you'll get type warnings if you use it where a different type is expected.
      • maccard 12 hours ago |
        Even for the non ide folks - vim emacs and vs code all have excellent support for that.
        • ranger_danger 11 hours ago |
          How does vim support this? I thought you had to use custom scripts/extensions to do it?
          • maccard 10 hours ago |
            Sorry I wasn’t clear - all those editors have simple (ish) plugins that support it
    • UebVar 16 hours ago |
      If you don't use an IDE, you are doing it wrong, plain and simple.

      Editing png with a text editor is also much harder than editing ppm. But there is no reason to consider this usecase when defining a image format.

      • alexvitkov 16 hours ago |
        You should never write code that's impossible to understand without fancy IDE features. If you're writing such code, the best thing you can do for yourself long term is switch to a text editor without LSP (read Notepad) right now, which will force you to start writing sane code.

        This is true for any language, but it's especially true for C++, where most large codebases have tons of invisible code flying around - implicit casts, weird overloads, destructors, all of these possibly virtual calls, possibly over type-erased objects accessed accessed via smart pointers, possibly over many threads - if you want to stand any chance of even beginning to reason about all that you NEED to see the actual, concrete, scientific types of things.

        • Jyaif 16 hours ago |
          > You should never write code that's impossible to understand without fancy IDE features

          with Rust that ship has sailed

          • jeltz 16 hours ago |
            I code Rust just fine without any fancy IDE you should give it a shot. The languages I find hardest to code without fancy IDE features are C and C++ due to their implicit casts. Rust is typically easy to code without IDE features due to its strong type system, lifetimes and few implicit casts.
            • zbentley 14 hours ago |
              Rust is one of my favorite new languages, but this is just wrong.

              > few implicit casts

              Just because it doesn't (often) implicitly convert/pun raw types doesn't mean it has "few implicit casts". Rust has large amounts implicit conversion behavior (e.g. deref coercion, implicit into), and semi-implicit behavior (e.g. even regular explicit ".into()" distances conversion behavior and the target type in code). The affordances offered by these features are significant--I like using them in many cases--but it's not exactly turning over a new leaf re: explicitness.

              Without good editor support for e.g. figuring out which "into" implementation is being called by a "return x.into()" statement, working in large and unfamiliar Rust codebases can be just as much of a chore as rawdogging C++ in no-plugins vim.

              Like so many Rust features, it's not breaking with specific semantics available in prior languages in its niche (C++); rather, it's providing the same or similar semantics in a much more consciously designed and user focused way.

              > lifetimes

              How do lifetimes help (or interact with) IDE-less coding friendliness? These seem orthogonal to me.

              Lastly, I think Rust macros are the best pro-IDE argument here. Compared to C/C++, the lower effort required (and higher quality of tooling available) to quickly expand or parse Rust macros means that IDE support for macro-heavy code tends to be much better, and much better out of the box without editor customization, in Rust. That's not an endorsement of macro-everything-all-the-time, just an observation re: IDE support.

              • jeltz 12 hours ago |
                Have you actually tried coding Rust without IDE support? I have. I code C and Rust professionally with basically only syntax highlighting.

                As for how lifetimes help? One of the more annoying parts of coding C is to constantly have to look up who owns a returned pointer. Should it be freed or not?

                And I do not find into() to be an issue in practice.

        • adrian_b 15 hours ago |
          While the C language has a lot of bad implicit casts that should have never been allowed, mainly those involving unsigned types, and which have been inherited by its derivatives, implicit casts as a programming language feature are extremely useful when used in the right way.

          Implicit casts are the only reason for the existence of the object-oriented programming languages, where any object can be implicitly cast to any type from which it inherits, so it can be passed as an argument to any function that expects an argument of that type, including member functions.

          The whole purpose of inheritance is to allow the programmer to use implicit casts. Otherwise, one would just declare a structure member of the class from which one would inherit in the OOP style and a virtual function table pointer, and one could write an identical program with the OOP program, but in a much more verbose way.

          (In the C language, not only the implicit mixed signed-unsigned casts are bad, but also any implicit unsigned-unsigned casts are bad, because there are 2 interpretations of "unsigned" frequently used in programs, as either non-negative numbers or as modular numbers, and the direction of the casts that do not lose information is reversed for the 2 interpretations, i.e. for non-negative numbers it is safe to cast only to a wider type, but for modular numbers it is safe to cast only to a narrower type. Moreover, there are also other interpretations of "unsigned", i.e. as binary polynomials or as binary polynomial residues, which cannot be inter-converted with numbers. For all these 4 interpretations, there are distinct machine instructions in the instruction sets of popular CPUs, e.g. in the x86-64 and Aarch64 ISAs, which may be used in C programs through compiler intrinsics. Even worse is that the latest C standards specify that the overflow behavior of "unsigned" is that of modular numbers, while the implicit casts of "unsigned" are those of non-negative numbers. This inconsistency guarantees the existence of perfectly legal C programs, without any undefined behavior, but which nonetheless compute incorrect "unsigned" values, regardless which interpretation was intended for "unsigned".)

          • AlotOfReading 13 hours ago |
            "Non-negative" unsigneds can be validly cast to smaller types. That's why saturating_cast() exists. There are modular numbers where casting to a smaller value is likewise unsafe at a logical level. Your LCRNG won't give you the right period when downcast, even if the modulus value is unchanged.
          • alexvitkov 13 hours ago |
            > Otherwise, one would just declare a structure member of the class from which one would inherit in the OOP style and a virtual function table pointer, and one could write an identical program with the OOP program, but in a much more verbose way.

            No, you don't have to do that. Once you start thinking about memory and manually managing it, it you'll figure out there's simpler, better ways to structure your program, rather than having a deep class hierarchy with a gazillion heap-allocated objects, each with distinct lifetime, all pointing at each other.

            Here's a trivial example. Say you're writing a JSON parser - if you approach it with an OOP mindset, you would probably make a JSONValue class, maybe subclass it with JSONNumber/String/Object/Array. You would walk over the input string and heap allocate JSONValues as you go. The problems with this are:

                1. Each allocation can be very slow as it can enter the kernel
                2. Each allocation is a possible failure point, so the number of failure points scales linearly with input size.
                3. When you free the structure, you must walk over the entire tree and free each obejct one by one.
                4. The output of this function is suboptimal as the memory allocator can return values that are far away in memory.
            
            There's an alternate approach that solves all these problems. If you're thinking about the lifetimes of your data, you would notice that this entire data structure is used and discarded at once, so you allocate a single big buffer for all the nodes. You keep a pointer to the head of that buffer, and when you need a new node, you stick it in there and advance the pointer by its size. When you're done you return the first node, which also happens to be the start of the buffer.

            Now you have a single point of failure - the buffer allocation, your program is way faster, you only need to free one thing when you're done, and your values are tightly packed in memory, so whatever is using its output will be faster as well. You've spent just a little time thinking about memory and now you have a vastly superior program in every single aspect, and you're happy.

          • knome 13 hours ago |
            inheritance isn't required for object oriented programming. the primary facet of oop is hiding implementation details behind functions that manipulate that data.

            adding values to a dict via add() and removing them via remove() should not expose to the caller if the underlying implementation is an array of hash indexed linked lists or what. the implementation can be changed safely.

            inheritance is orthogonal to object orientation. or rather, inheritance requires oop, but oop does not require inheritance.

            golang lacks inheritance while remaining oop, for instance, instead using interfaces that allows any type implicitly defining the specified interface to be used the.

            • adrian_b 11 hours ago |
              "Hiding implementation details" means the same as "hiding the actual data type of an object", which means the same as "performing an implicit cast whenever the object is passed as an argument to a function".

              Using different words does not necessarily designate different things. Most things that are promoted at a certain time by fashions, like OOP, abuse terminology by giving new names to old things in the attempt of appearing more revolutionary than they really are.

              Most classic works about OOP define OOP by the use of inheritance and of virtual functions a.k.a. dynamic polymorphism. Both features have been introduced by SIMULA 67 and popularized by Smalltalk, the grandparents of all OOP languages.

              When these 2 features are removed, what remains from OOP are the so-called abstract data types, like in CLU or Alphard, where you have data types that are defined by the list of functions that can process values of that type, but without inheritance and with only static polymorphism (a.k.a. overloading).

              The example given by you for hiding an implementation is not OOP, but it is just the plain use of modules, like in the early versions of Ada, Mesa or Modula, which did not have any OOP features, but they had modules, which can export types or functions whose implementations are hidden.

              Because all 3 programming language concepts, modules, abstract data types and OOP have as an important goal preventing the access to implementation details, there is some overlap between them, but they are nonetheless distinct enough so that they should not be confused.

              Modules are the most general mechanism for hiding implementation details, so they should have been included in any programming language, but the authors of most OOP languages, especially in the past, have believed that the hiding provided by granting access to private structure a.k.a. class members only to member functions is good enough for this purpose. However this leads sometimes to awkward programs where some classes are defined only for the purpose of hiding things, for which real modules would have been more convenient, so many more recent versions of OOP languages have added modules in some form or another.

              • knome 8 hours ago |
                I'll readily admit the languages were marketed that way, but would argue inheritance was a functional, but poor, imitation of dynamic message dispatch. Interfaces, structural typing, or even simply swapping out object types in a language with dynamic types does better for enabling function-based message passing than inheritance does, as they avoid the myriad pitfalls and limitations associated with the technique.

                Dynamic dispatch can be accomplished in any language with a function type by using a structure full of functions to dispatch the incoming invocations, as Linux does in C to implement its file systems.

          • uecker 12 hours ago |
            I am actually ok with the conversions and C and think they are quite convenient. Unsigned in C is modular. I am not sure what you mean by the "latest C standards specify". This did not change. I also do not understand what you mean by the "implicit cast of unsigned are those of non-negative numbers". This seems wrong. If you convert to a larger unsigned type, the value is unchanged and if you convert to a smaller, it is reduced modulo.
            • adrian_b 11 hours ago |
              In older C standards, the overflow of unsigned numbers was undefined.

              In recent C standards, it has been defined that unsigned numbers behave with respect to the arithmetic operations as modular numbers, which never overflow.

              The implicit casts of C unsigned numbers are from narrower to wider types, e.g. from "unsigned short" to "unsigned" or from "unsigned" to "unsigned long".

              These implicit casts are correct for non-negative numbers, because all values that can be represented as e.g. "unsigned short" are included among those represented by "unsigned" and they are preserved by the implicit casts.

              However, these implicit casts are incorrect for modular numbers, because they attempt to compute the inverse of a non-invertible function.

              For instance, if you have an "unsigned char" that is a modular number with the value "3", it is incorrect to convert it to an "unsigned short" modular number with the value "3", because the same "unsigned char" "3" corresponds also to 255 other "unsigned short" values, i.e. to 259, 515, 781, 1027 and so on.

              If you have some very weird reason when you want to convert a number modulo 256 to a number modulo 65536 by choosing a certain number among those with the same residue modulo 256, then you must do this explicitly, because it is not an information-preserving conversion.

              If on the other hand you interpret a C "unsigned" as a non-negative number, then the implicit casts are OK, but you must add everywhere explicit checks for unsigned overflow around the arithmetic operations, otherwise you will obtain erroneous results.

              • uecker 9 hours ago |
                The C89 standard has "A computation involving unsigned operands can never overflou. because a result that cannot be represented b! the resulting unsigned integer type is reduced modulo the number that is one greater thnn the largest value that can be represented by the resulting unsipned integer type" (OCR errors) You can finde a copy here: https://web.archive.org/web/20200909074736if_/https://www.pd...

                Mathematically, there is no clearly defined way how one would have to map from one residue system in modular arithmetic to the next, so there is no "correct" or "incorrect" way. Mapping to the smallest integer in the equivalency class makes a lot of sense though, as it maps corresponding integers to itself when going to a larger type and and the reverse operation is then the inverse, and this is exactly what C does.

      • foooorsyth 15 hours ago |
        Not everyone reading your code will be using an IDE. People may be passively searching your code on GitHub/gerrit/codesearch.

        val/var/let/auto declarations destroy the locality of understanding of a variable declaration without an IDE + a required jump-to-definition of a naive code reader. Also, a corollary of this problem also exists: if you don’t have an explicit type hint in a variable declaration, even readers that are using an IDE have to do TWO jump-to-definition actions to read the source of the variable type.

        eg.

        val foo = generateFoo()

        Where generateFoo() has the signature fun generateFoo(): Foo

        With the above code one would have to jump to definition on generateFoo, then jump to definition on Foo to understand what Foo is. In a language that requires the explicit type hint at declaration, this is only one step.

        There’s a tradeoff here between pleasantries while writing the code vs less immediate local understanding of future readers / maintainers. It really bothers me when a ktlint plugin actually fails a compilation because a code author threw in an “unnecessary” type hint for clarity.

        Related (but not directly addressing auto declarations): “Greppability is an underrated code metric”: https://morizbuesing.com/blog/greppability-code-metric/

        • edflsafoiewq 15 hours ago |
          If you accept f(g()), you've already accepted that the type of every expression is not written down.
          • foooorsyth 15 hours ago |
            I don’t particularly accept f(g()). I like languages that require argument labels (obj-c, swift). I would welcome a language that required them for return values as well. I’d even enjoy a compiler that injected omitted ones on each build, so you can opt to type quickly while leaning on the compiler for clarity beyond build time.
            • gpderetta 3 hours ago |
              Argument labels are equivalent to variable names. You still have them with auto. In either case you don't see the actual type.
        • UebVar 15 hours ago |
          The argument is tautological.

          I want to use a text editor => This is the wrong tool => Yes, but I want to use a text editor.

          These people do use the wrong tooling. The only way to cure this grievance is to use proper tooling.

          The github webui has some ide features, such as symbol search. I don't see any reason why not use a proper ide. github.dev is a simple click in the ui away. When you use gerrit, do a local checkout, that's one git command.

          If you refuse to use the correct tools for the job, your experience is degraded. I don't see a reason to consider this case when writing code.

          • foooorsyth 15 hours ago |
            Have you ever worked in a large organization with many environments? You may find yourself with a particular interface that you don’t know how to use. You search the central code search tool for usages. Some other team IS using the API, but in a completely different environment and programming language, and they require special hardware in their test loop, and they’re located in Shanghai. It will take you weeks to months to replicate their setup. But your goal is to just understand how to use your version of the same API. This is incredibly common in big companies. If you’re in a small org with limited environments it’s less of an issue.
            • UebVar 11 hours ago |
              I have worked in big environments. My idea about "big" might be naive, environments spanning different Oses and different, including old languages like fortran and pascal. But I never been in a situation where I couldn't check out said code, and open it in my ide and build it. If you can't that sounds like a another case of deficient tooling. Justifying deficient tooling.

              These where not some SWE wonderlands either. The code was truly awful at times.

              The Joel test is 25 years old. It's a industry standard. I, and many other people consider it a minimum requirement for software engineering. If code the "2. Can you make a build in one step?" requirement i should be ide-browsable in one step.

              If it takes weeks to replicate a setup the whole environment is deeply flawed. The one-step build is the second point on the list because Joel considered it the second most important thing, out of 12.

              • foooorsyth 6 hours ago |
                My situation: hardware company, over 100 years old. I’ve found useful usage examples of pieces of software I need to use, but only on an OS we no longer ship, from a supplier we no longer have a relationship with, that runs on hardware that we no longer have. The people that know how to get the dev environment up are retired.

                In those cases, I’m grateful for mildly less concise languages that are more explicit at call and declaration sites.

        • adrian_b 15 hours ago |
          I do not agree that using an IDE matters.

          If you cannot recognize the type of an expression that is assigned to a variable, you do not understand the program you are reading, so you must search its symbols anyway.

          Writing redundantly the type when declaring the variable is of no help when you do not know whether the left hand side expression has the same type.

          When reading any code base with which you are not familiar, you must not use a bad text editor, but either a good text editor designed for programmers or any other tool that allows fast searching for the definitions of any symbols encountered in the source text.

          Adding useless redundancy to the source text only bloats it, making reading more difficult, not easier.

          I never use an IDE, but I always use good programming language aware text editors.

        • chrisoverzero 13 hours ago |
          > if you don’t have an explicit type hint in a variable declaration, even readers that are using an IDE have to do TWO jump-to-definition actions to read the source of the variable type.

          This isn’t necessarily the case. “Go to Definition” on the `val` goes to the definition of the deduced type in the IDEs and IDE-alikes I’ve ever used.

      • ranger_danger 11 hours ago |
        Can we turn down the dogmatism please? I think you will find that there are other equally valid perspectives if you look around, and that the world is not so black and white.
    • readyplayernull 15 hours ago |
      Isn't inline more "undeterministic" than auto? That is way older and used everywhere.

      I'd like auto functions.

      • jayd16 12 hours ago |
        Isn't it mostly meaningless outside of the syntax sugar of putting code in the header?
    • FpUser 15 hours ago |
      I am in a middle ground. Usually do not use auto but in the cases like:

         for (auto member : set_of_members)
      
      and some other that are similar by nature auto is a god blessing.
      • secondcoming 12 hours ago |
        Except this makes a copy of each member in set_of_members, which is probably not what you want.

        https://godbolt.org/z/1YnEs1M34

        • FpUser 10 hours ago |
          This was to illustrate a point of auto rather than intricacies of copying, referencing. I know what I want and am familiar with auto&, const auto&, auto&& etc. etc.
    • zabzonk 15 hours ago |
      Complicated template types, where you have a general idea of the type but you don't want or need to spell it all out (it might be very long) when the compiler can easily do it for you.
    • jcelerier 15 hours ago |
      I have never seen anyone come back to typing types everywhere after using auto for more than a couple months.

      Use types when they are needed and use the tools at your disposal (IDEs BT every text editor has clang language server integration nowadays)

      > with light documentation which means reading the code,

      You have to read the code anyways, documentation is impossible to trust. There isn't one big library for which I didn't have to go read the code at some point. Two weeks ago I had to go read the internals of msvcrt, Microsoft's C runtime, to understand undocumented features of process setup on windows. I had to go read standard library code thousands of times, and let's not talk about UI libraries.

      • coffeeaddict1 9 hours ago |
        > Use types when they are needed and use the tools at your disposal (IDEs BT every text editor has clang language server integration nowadays)

        While I agree that auto is helpful, the amount of times I had to wait for clangd (or whatever the IDE is using) to parse a .cpp file and deduce the underlying type is frustrating. It happens too often with every IDE (Qt Creator, CLion, VS Studio, VS Code, etc...) I've tried whenever I'm programming with a non-desktop machine that's not super beefy.

        Plus I often use Github to search for code when I'm trying out a new lib so having the type spelled out is extremely helpful.

    • nly 14 hours ago |
      It's not 1993. IDEs tell you the type if you hover over the auto. Or control and click takes you to the type definition.

      You have to weigh up the cost of going through the code and changing all the type declarations

          auto x = foo();
      
      If you change the return type of foo here you don't have to change 300 call sites. Personally i'd rather change it in one place than 300.
      • AlotOfReading 14 hours ago |
        Editor type deduction is surprisingly unreliable sometimes.
      • ninkendo 13 hours ago |
        What about code reviewers? Show me a code review system that lets you hover over the value to see the type… none of the ones I’ve used can do it.

        For that matter anyone reading the code from a web browser in any other context.

        • dgfitz 12 hours ago |
          Fwiw, I agree. I also pull down the branch under review in parallel to the web code review. You’d (probably) not be surprised by the number of times I’ve done this and the code doesn’t even build.
        • ranger_danger 12 hours ago |
          I wonder if most of the reason people use auto is just to save time when typing... if the IDE could auto-resolve the type in the source code when they use auto... would that be a better compromise?
        • ogoffart 11 hours ago |
          Even without auto you have the problem.

              return foo().bar();
          
          No `auto` and you still don't know the return type of foo. And knowing the type might not be the only reason you'd want an IDE anyway. What is `foo()` doing? I want to be able to easily jump to the definition of that function to verify that the assumption taken by the calling function are correct.
        • ch33zer 10 hours ago |
          This was probably rhetorical but metas code review tool runs LSP and gives you clickable types where clicking takes you to the definition.
    • Koshkin 14 hours ago |
      ‘auto’ (in C++) and ‘var’ (in C# and Java) is a blessing, makes code much less verbose. Also good for refactoring - less code to change.
      • BalinKing 14 hours ago |
        I’m only a C++ amateur, but IMHO C++ vs C#/Java isn’t really a fair comparison here—the latter doesn’t have template shenanigans and so types are much more transparent to the reader (by which I mean that you don’t have to execute a dynamically-typed program in your head to get from the term on the right-hand side to the type on the left).
      • bigstrat2003 10 hours ago |
        Verbosity is not bad. When it makes the code clearer, it is even a good thing.
        • ithkuil 9 hours ago |
          Complex type parameters make explicit typing highly impractical to be used all the times

          I like rust's approach in that it allows a mixture of explicit types and type inference using placeholders

          For example: "let x : Result<Foo<int, _>, _> = make_foo();"

    • dataflow 14 hours ago |
      > I am so shocked at how many people use `auto` in C++.

      I agree with you! But:

      > I can not think of a worse thing to do to your code in terms of readability and future maintainability.

      Well, I definitely can. Using macros is one ;)

      > I am trying to learn a new library right now with light documentation which means reading the code, and between typedefs, auto

      I disagree with you on the typedefs. They're much better than auto. Auto doesn't provide any type checking, it works whatever the type is. Typedef tell you what the expected type actually is.

    • benreesman 14 hours ago |
      Herb Sutter has a pretty good explanation: https://herbsutter.com/2013/08/12/gotw-94-solution-aaa-style...
    • lairv 14 hours ago |
      Using auto in function parameters to have implicit templates is very cursed
      • jayd16 12 hours ago |
        I'm pretty supportive of auto and var, etc. in languages but parameters seem like a step too far.
    • cesaref 13 hours ago |
      I can certainly relate to this experience. I remember when it was introduced, I was very wary of getting too much 'auto' into the codebase, certainly as the team could be a bit 'gung-ho' adopting stuff just because it was there.

      However, in hindsight, I think I was being overly conservative, and it worked out well, and adoption didn't cause any obvious problems.

      Your concerns about learning a new library are valid, but the problem is the library if it's not clear, or well documented. To lay responsibility for this at the door of auto is a stretch. You can write great and terrible code with a number of language features (dubious use of goto is the classic example), and it sounds like you are tackling a library which could do with some love, either in documentation, or to clarify it's conventions.

    • kgeist 13 hours ago |
      My rule of thumb is to use auto only when the type is obvious from the context. I think it's a sane compromise between readability and non-verbosity.
    • MathMonkeyMan 13 hours ago |
      I write my code with the assumption that the reader does _not_ have access to a "smart" IDE.

      I use `auto` when the type is obvious or doesn't really matter, and I seldom create aliases for types.

      I feel like having verbose type names at function boundaries and using `auto` for dependent types is the sweet spot. I'll often avoid `auto` when referring to a class data member, so the reader doesn't have to refer to the definition.

          void foo(const std::multimap<double, Order>& sells) {
              for (const auto& [price, order] : sells) {
                  // ...
              }
          }
      
      but also

          void foo(const OrderBook& book) {
              const std::multimap<double, Order>& sells = book.sells;
              for (const auto& [price, order] : sells) {
                  // ...
              }
          }
      
      `auto` is convenient for iterators. Which of the following is better?

          auto iter = sells.begin();
          std::multimap<double, Order>::const_iterator iter = sells.begin();
      • Koshkin 13 hours ago |
        Off-topic, but just wanted to note that using floating-point numbers as keys may be generally a bad idea (unless you use a custom comparator that takes into account the error that can accumulate during calculations).
        • mgaunard 12 hours ago |
          especially for an order book...
    • pjmlp 13 hours ago |
      Programming without IDE is so 1970's...

      Having said this, I usually only use type inference when the types are obvious from context.

      • jayd16 13 hours ago |
        Was this tongue in cheek? If you _can_ use inference it was at least obvious enough to the compiler. Otherwise you're just saying "I use it when I feel like it."
        • pjmlp 11 hours ago |

             auto x = func(); // no idea about func return type
          
             auto x = new Widget(); // DRY
          
             auto sum (auto a, auto b); //  template function without boilerplate 
          
          Use the same principle in other contexts.
    • asveikau 13 hours ago |
      There's so much redundancy built into the language if you don't. Imagine:

          std::shared_ptr<T> p = std::make_shared<T>();
      
      Then replace T with a very long type. And that's not the most verbose example, just an early one that popped into mind.

      Then you have lambdas. Imagine assigning a lambda into a stack variable without auto, also keeping in mind that std::function adds overhead.

      • secondcoming 12 hours ago |
        That example isn't what OP is talking about, because it's obvious what the type of p is because it's on the same line:

            auto p = std::make_shared<T>();
        
        whereas the following isn't clear and isn't necessarily correct without looking up what the return type of foo() actually is:

            auto p = foo();
        • asveikau 12 hours ago |
          I'll agree that your second example is less readable than the first..

          This could be mitigated with the name of foo() being more descriptive.

          If the return type is particularly wordy, auto could still be appropriate.

          • unleaded 10 hours ago |
            > This could be mitigated with the name of foo() being more descriptive.

            welcome back Hungarian notation

            • jeremyjh 10 hours ago |
              auto conn = createConnection();

              What does this have to do with hungarian notation?

              • pests 10 hours ago |
                You’re thinking of it wrong.

                This works better:

                auto uasStudents = getClassList()

                In this case, “uas” prefix standing for an unsafe array of strings.

                Then say you validate the list

                auto sasStudents = validate(uasStudents)

                (Now it’s a safe array of students!)

                • dullcrisp 9 hours ago |
                  I feel like I’m missing a joke.
              • powercf 8 hours ago |
                Alone this looks like a reasonable use of auto. In a real codebase, there may be two (or twenty) different connection-like things, multiple of which may be reasonably to call in this context.

                The "Hungarian notation" comment is correct - it's not strictly Hungarian notation, but annotating function (or variable) names when the language has a type system representing this same information is the same idea with the same problems as Hungarian notation.

            • asveikau 9 hours ago |
              lpwszThanks.

              In seriousness, no, that's not what I'm suggesting, and I find it an unusual thing to read from my comment. I'm saying a descriptive name for foo() can give you a hint about what the type is, even if it doesn't literally and directly tell you what the type is.

        • shaklee3 2 minutes ago |
          this is required for writing generic code when the function may return different types
    • maccard 12 hours ago |
      Im as shocked as you are that people rely on textual representations and ignore all the powerful tooling available to them for understanding code.

      Every editor I use has tools that will provide this information in a single keystroke, macro or cluck. If you actively choose to avoid using tools to read code, I shouldn’t suffer for it.

      • mgaunard 12 hours ago |
        if you need tools it just means the code is sub-par
        • maccard 12 hours ago |
          Grep is a tool. But if I grep foo, it can’t tell me the difference between a function called foo, a variable called foo, or any other types that may have a foo, or a comment with foo in it. Even vscode can do “show me all uses of foo” in a single click, and be perfectly correct,
          • mgaunard 10 hours ago |
            It's not perfectly correct, and that actually what makes it dangerous.
        • orf 7 hours ago |
          That take is so comically nonsensical I question why you’d even contribute it.

          I have no doubt you read and write code without any tools.

    • ninkendo 12 hours ago |
      There’s a lot of arguments here where people are saying basically, “auto is bad because you can …” it “auto is great because you can …”, as if the two are mutually exclusive or something.

      It’s like saying “knives are bad because you can kill someone” vs “knives are good because they can help make food”… nobody thinks of knives as being an exclusively good or exclusively bad thing; we all understand that context is key, and without context it’s meaningless.

      Instead I feel it would be a lot more illuminating if the discussion centered around rules of thumb… which contexts auto is good, vs when it’s bad. There’s probably no complete list, but a few heuristics would be great.

      My 2¢:

      Explicit type declaration is a form of documentation, used to tell the casual reader (ie. Often in a web browser, code review, or someone seeing it copy/pasted as a snippet[0]) the meaning of a piece of code. It’s even better than comments: it’s guaranteed not to be a lie, or the code wouldn’t compile.

      I’ve seen this all the time working in Rust, Swift, typescript, etc… sometimes an expression is super complicated, and the type checker can infer the type just fine, and my IDE even shows the type in an inlay… but I still worry that if these weren’t available, the expression would look super confusing to a reader. So I make the type explicit to aid in overall readability.

      When to do this or not is a judgement call… different people will come to different conclusions. But it’s like any other form of line-level documentation. Sometimes the code is self explanatory, and sometimes it’s not. But be kind to the casual reader and use explicit types when it’s very non-obvious what the type would be.

      [0] ie. Anyone without immediate access to an IDE or something else that would show the type.

      • ranger_danger 12 hours ago |
        > we all understand that context is key

        Unfortunately I think this either this isn't actually the case for many people, or too often they just never even stop to consider that other perspectives might be possible, better or even more common than their own.

        In chatting with technical people online for the last 30 years, the biggest issue I have always had is their attitude. IRC seems the worst for it but every platform has this problem in my experience.

        God complexes visible from space run rampant, people always speaking in absolutes and seeing things as black and white, complete lack of empathy and humility etc.

        I think most arguments in the world, and even other things like crime, might actually just stem from people's inability to communicate with each other effectively.

    • nurettin 12 hours ago |

         int wtf = omgtype(); // and read the compiler error
    • DonHopkins 12 hours ago |
      Some kinky C++ programmers have such a sexual fetish for using `auto` that they enjoy holding their breath as long as possible while writing code, before ever declaring any explicit type names. That's called auto-erotic asphyxiation!
    • ok123456 11 hours ago |
      Auto is preferred for assignment because it eliminates a whole class of errors involving unintentional construction. Dropping a const is the conanical example.

      Auto in a function signature is syntactic sugar for introducing a template parameter. It needs to be monomorphized at some point to generate code.

    • nox101 11 hours ago |
    • fooker 11 hours ago |
      Like everything in life, it has to be used in moderation.

      auto it = data.begin();

      Is a lot more readable than

      std::vector<std::pair<std::vector<foo>, int>::iterator it = ....

      • gigatexal 10 hours ago |
        Oh boy! A vector of tuples of vectors and ints? Crazy
    • ch33zer 10 hours ago |
      One area where auto is necessary is in coroutines. The types are so hideous and abstract that writing them out is guaranteed to be less readable than using auto and accepting the your types are some blend of compiler derived and coroutine library magic.
    • summerlight 9 hours ago |
      My take is that `auto` is basically a tool to reduce local redundancies rather than typing convenience. Rule of thumb: you should avoid `auto` unless it actually improves readability (e.g. significant reductions of syntactic redundancies), or there is no other option.
    • musicale 8 hours ago |
      > I am so shocked at how many people use `auto` in C++

      Well I blame C++ for calling it "auto" in the first place. Fortunately this is easily fixed:

          #define let auto
          #define var auto
      
      ;-)
    • paulddraper 7 hours ago |
      > it is a tedious exercise to figure out something's type, to go look up its function, to see what type that is returning

      To cite your previous sentence, why don't you use your IDE?

      Or is this a magnetized needle sort of situation.

    • RcouF1uZ4gsC 6 hours ago |
      Honestly, use a good IDE.

      Jetbrains can annotate your source with what the actual type is.

      and auto can help future maintainability if you need to change concrete types that have the same API surface.

  • emcell 10 hours ago |
    every time i see stuff like this, I hope I never have to work on c++ projects again.