[HN Gopher] Is C++ Doomed?
       ___________________________________________________________________
        
       Is C++ Doomed?
        
       Author : ingve
       Score  : 62 points
       Date   : 2022-02-23 20:32 UTC (2 hours ago)
        
 (HTM) web link (tednesday.wordpress.com)
 (TXT) w3m dump (tednesday.wordpress.com)
        
       | LoveGracePeace wrote:
       | Simple, it's a war between those who are Pascal inclined (and
       | probably liked Macs and Steve Jobs) e.g.
       | (https://www.quora.com/Why-does-the-type-go-after-the-variabl...
       | - add Rust, Kotlin and Scala) and those who know the best
       | languages, the ones that are actually used in production at
       | companies large and small worldwide: C, C++, Java and C#. IOW, if
       | the language does "type varname" then it's correct, if it does
       | "varname: type" then it is doomed. It's sarcasm but it's true.
        
       | pubby wrote:
       | I agree that C++ is a mess of a design, but I don't think this
       | article quite gets how. RAII and move semantics are two of C++'s
       | best features, and the ones that any replacement - like Rust -
       | will copy.
       | 
       | To me, bigger flaws with C++ are that it's too big. The language
       | is bloated with multiple features that accomplish similar things,
       | but each with their own odd corner cases and interference.
       | Anytime there's an issue from a language component, the solution
       | is to tack another language component on top of things. See for
       | example the progression of Macros -> Classes -> Templates ->
       | Constexpr -> Concepts. Each tried to fix flaws in the previous
       | design, but each introduction made the language more complicated,
       | not less.
       | 
       | > But exceptions come at a performance cost.
       | 
       | I mean it's slow to throw, but non-throwing code paths are hardly
       | affected. Typical implementations store the exception unwinding
       | stuff off to the side, so the only cost is binary size.
       | 
       | > That means you need to write code that wraps every possible
       | resource in RAII logic.
       | 
       | Consider using scope guards. You can write them directly in your
       | code, without a separate class per resource.
       | int* ptr = malloc(...);         auto guard = defer([&]{
       | free(ptr); });
        
         | mFixman wrote:
         | From a post in /r/cpp [1],
         | 
         | > As of C++20, the right way to write a trivial getter in C++
         | looks like `[[nodiscard]] constexpr auto GetFoo() const
         | noexcept -> Foo { return foo_; }`
         | 
         | C++>11 is such a mess than most useful changes are invisible to
         | most users due to bloat.
         | 
         | I think it's way past time to call it quits and abandon active
         | development of the language. New features will come from other
         | languages without 35 years of baggage.
         | 
         | [1]
         | https://www.reddit.com/r/cpp/comments/swv6wh/when_not_to_use...
        
           | UncleMeat wrote:
           | I'm not sure it is remotely reasonable to choose a language
           | for a project based on the verbosity of accessor signatures
           | when being maximally pedantic.
           | 
           | C++ is indeed a mess but there is almost no alternative for
           | existing enormous codebases than to keep making it better.
           | The industry will slowly adopt memory safe system languages,
           | but that's going to take ages to go beyond greenfields and
           | absolutely security-critical systems.
        
         | Rusky wrote:
         | I do think the article's criticism of C++'s move semantics is
         | fair. Rust's "copy" of both RAII and move semantics is
         | drastically simpler, and C++'s version is part of what makes
         | the language so huge.
        
       | Animats wrote:
       | The basic problem with C++ is that it has has hiding without
       | safety. C has neither, and most newer languages have both.
       | Attempts to add safety to C++ via templates always seem to leak
       | raw pointers, since many APIs never converted to C++. It's
       | significant that there isn't a C++ Linux kernel API, where you
       | use C++ strings for everything and get rid of the null-terminated
       | stuff.
       | 
       | You see this in the original poster's example. He's doing C-type
       | I/O in C++. Of course it doesn't work cleanly.
       | 
       | It's frustrating. C++ is object-oriented. The cool kids don't
       | like that. So, Rust isn't object-oriented; it uses traits and
       | generics. Those are different enough that there is no one-to-one
       | way to convert modern C++ into safe Rust. It takes a redesign.
       | 
       | So we're kind of stuck.
       | 
       | If you're familiar with game engines, consider what it would take
       | to convert Unreal Engine to Rust.
        
         | dathinab wrote:
         | I would say many (not all) game engines could probably be
         | converted, by finding common pattern in usage of inheritance
         | and mapping it to rust patterns.
         | 
         | EDIT: Through automatizing this, or making C++ to rust bindings
         | for it would be hard.
         | 
         | I mean creating inheritance graphs for entities is as far as I
         | know already seen as an anti pattern since a while.
         | 
         | Things like ECM are nicely re-presentable in rust.
         | 
         | Many patterns of inheritance for code re-use tent to also not
         | be too hard to represent in rust.
         | 
         | But I would argue that even if rust had inheritance engines
         | like Unreal Engine would not be ported, at least not until they
         | do a major rewrite anyway. Because of it being a lot of work
         | with not too much intermediate gains.
        
           | biesnecker wrote:
           | There's a pretty wide gap, though, between "nicely
           | representable in rust" and "mechanically translatable from
           | the C++ implementation" that mostly requires human effort to
           | span.
        
             | dathinab wrote:
             | Yes, though even if rust had inheritance I don't think you
             | would have a machine translatable mapping good enough to
             | port a game engine.
             | 
             | Generally for any language which in design isn't "somewhat
             | close to C++" I don't think this would work.
        
       | sto_hristo wrote:
       | When i do my periodic refresh runs c++, i often question myself -
       | why i'm bothering with it? C++ seems more about programming c++
       | itself than programming against the actual problem. At which
       | point i reason that if i ever need to go native and fast, i'd
       | simply use C, and for the higher level use not-c++.
        
       | uoaei wrote:
       | Gonna be a long while before safety and certification bodies for
       | industries like aerospace move away from C++. All the tools for
       | the past few decades focus on C++ as the main systems language in
       | these cases.
       | 
       | Rust is great as a spiritual successor with more stable
       | performance (in general) but the institutional inertia with C++
       | is strong.
        
         | nine_k wrote:
         | Aerospace industry? C++? Frightening.
         | 
         | I thought Ada is prevalent in this space.
        
           | ghosty141 wrote:
           | I'm not in the industry but from what I know it's a split
           | between Ada/C/C++. More space related applications tend to be
           | C/C++ (satelites, rovers, etc.) while the aero-side uses more
           | Ada (Boeing, Airbus etc.).
        
           | Jtsummers wrote:
           | Ada never really "won". It was mandated by DOD, but the
           | industry more or less rebelled at the idea. Fortran was
           | pretty common, but C and C++ are the dominant languages. The
           | last greenfield aerospace (avionics) project I was on, we
           | were given a choice and it seemed that we (and everyone else)
           | went with C or C++. There are a lot of static analysis tools
           | now that cover most of what you get from Ada (and then some,
           | even with SPARK), but the choice was really based on
           | familiarity for potential new hires. You also use strict
           | subsets of C or C++ in this field, not every feature will be
           | available. (I wish it had been in Ada, a lot of issues in the
           | system wouldn't have happened in Ada, but they did eventually
           | get worked out anyways.)
        
         | rch wrote:
         | What's the state of Rust/C++ interop currently? Naively it
         | seems like allowing Rust in C++ projects could be a reasonable
         | way forward, but I haven't tried it personally yet.
        
           | xscott wrote:
           | I think you're implicitly saying that Rust solves the
           | problems in the article, and so migrating C++ projects to use
           | Rust is a path forward. However, the blog author's first
           | example was implementing a "contiguous circular queue", so
           | you should try to do that in Rust. Of course Rust already has
           | VecDeque<T>, but then C++ already has std::deque<T> too
           | (although not promised to be contiguous). So the exercise is
           | to implement your own, from scratch, without just wrapping a
           | type that does the dirty work for you.
           | 
           | If you look closely at the implementation details for
           | VecDeque<T>, you'll find a lot of complexity you might not
           | expect. To do it efficiently and correctly, you _need_ to
           | work with unitialized memory. So there will be unsafe blocks
           | in there. A VecDeque <T> is built of a RawVec<T>, which uses
           | a Unique<T>, which finally has a pointer, but also contains a
           | magical PhantomData<T>. Look at the code [0] [1] [2] [3],
           | look at the implementation details, read the comments, and
           | assess for yourself if it's easier or harder than C++.
           | 
           | Later in the article, he talks about exception safety. For
           | something like a container, the most likely exceptions are
           | that you've run out of memory, or you can't
           | move/copy/construct an item in the container. I'm honestly
           | not sure how Rust's builtin containers handle these problems.
           | (Panic?) But if you're comparing the two languages, the
           | apples and oranges matter.
           | 
           | All of this to say that Rust is not simple either. You can
           | program Rust by using its standard collections, and you can
           | do the same with C++. If you try to implement those
           | collections, say for learning/teaching data structures, both
           | languages seem painfully complicated to me.
           | 
           | [0] https://doc.rust-
           | lang.org/src/alloc/collections/vec_deque/mo...
           | 
           | [1] https://doc.rust-lang.org/src/alloc/raw_vec.rs.html#52
           | 
           | [2] https://docs.rs/ptr/0.1.0/src/ptr/lib.rs.html#39-47
           | 
           | [3] https://doc.rust-lang.org/src/core/marker.rs.html#679
        
             | ansible wrote:
             | > _Later in the article, he talks about exception safety.
             | For something like a container, the most likely exceptions
             | are that you 've run out of memory, or you can't
             | move/copy/construct an item in the container. I'm honestly
             | not sure how Rust's builtin containers handle these
             | problems. (Panic?)_
             | 
             | As I understand it yes, the containers will panic.
             | 
             | Part of the discussion about using Rust in the Linux kernel
             | is what to do about failed memory allocations. And this is
             | a problem more generally for Rust usage in embedded
             | systems.
             | 
             | So there may need to be support for alternative allocators
             | or something else.
        
               | steveklabnik wrote:
               | Part of the key is that they're not really "built in."
               | They're in the standard library. When you're in those
               | contexts, you just don't use the standard library.
               | Problem solved.
               | 
               | Now, the standard library is also getting support for
               | these things not panicking on allocation failure, and
               | when that's ready enough for those that want it (Rust for
               | Linux has pulled the changes into their tree, in my
               | understanding) then they could use them.
               | 
               | (I work on embedded systems with no dynamic allocation,
               | Rust is great.)
        
           | lazypenguin wrote:
           | It's fine, works well enough. Challenges are passing data
           | across the boundaries as the data types need to conform to
           | both sides. E.g. using a string in both languages without
           | doing copies means using a C-style string and now you've lost
           | some expressiveness in both languages.
           | 
           | Second challenge is smoothness of integration in build
           | systems. In C++ you might use CMake, in Rust you use Cargo,
           | so now who is authoritative? Do you use Cargo to build the
           | C++ code? Do you build your Rust projects in CMake?
        
           | cogman10 wrote:
           | C++ doesn't have a well defined ABI. So the interropt between
           | rust and C++ is mostly over a C ABI bridge. For the most
           | part, that's not too painful.
        
           | pcwalton wrote:
           | cxx, the high-level Rust/C++ interoperability layer, is
           | currently used in production at large scale.
        
         | flyingfences wrote:
         | In this aerospace company, we use C, no C++ allowed, to avoid
         | all the extra footguns that get in the way of safety and
         | certification. Rust will probably be the next choice on the
         | scene, but I expect it'll take as long to come as Ada took to
         | go.
        
           | dathinab wrote:
           | > Rust will probably be the next choice on the scene
           | 
           | I'm not sure about it.
           | 
           | I think Rust might be hard to certify for aerospace usage.
           | 
           | Not because it's bad, but because it's still complex. Just a
           | lot of negative side effects of complexity are contained due
           | to the compiler checks and design.
           | 
           | But that makes it harder to certify as I can tell.
        
           | potbelly83 wrote:
           | Off topic, but curious why Ada went?
        
           | jvanderbot wrote:
           | In this aerospace company, we use C, and sometimes a subset
           | of C++ that is basically C+epsilon. That's all new, however,
           | so each new project that goes that direction is walking a
           | dark forest.
           | 
           | We've got some Rust on the ISS, but honestly, the interest
           | from the serious FSW folks isn't there.
        
       | ziggus wrote:
       | User finds bag of M&Ms difficult to open:
       | 
       | "IS CANDY DOOMED?"
        
         | nine_k wrote:
         | I'd compare C++ more to tobacco than to candy: it once looked
         | like a good idea, but now its side effects are considered
         | dangerous.
        
       | Subsentient wrote:
       | As someone who uses both Rust and C++ frequently with a good
       | understanding of both languages, I can say that Rust is for the
       | mostpart more pleasant to work in, but Rust's handicap is that it
       | makes working in raw pointers when you really have to quite
       | painful.
       | 
       | If you need full raw pointers with aliasing mutable pointers, the
       | fact that fn drop() takes a &mut T is a problem that can only
       | currently be worked around with fugly hacks, like nesting your
       | data type in another pointless structure, not to mention the
       | giant pile of ambiguous UB that you risk whenever pointers and
       | references interact that could so easily be avoided if Rust would
       | just implement an -fno-strict-aliasing switch, but the devs
       | refuse on what appears to be ideological grounds. The result is
       | that for unsafe Rust, you get about the safety level of C much of
       | the time, because RAII and many language facilities are not safe
       | to use. Unsafe Rust is currently so dangerous that I feel much
       | safer reaching for C++ for some of it. Mutable reference
       | uniqueness, I think, should be enforced by the borrow checker and
       | NOT by pain of inescapable possible undefined behavior at the
       | assembler level.
       | 
       | Now for C++.
       | 
       | C++ is lacking a _LOT_ of useful safety features that Rust comes
       | with baked in, such as Mutexes owning their data, the borrow
       | checker, a good, fast atomic reference counting type, and things
       | that translate to more safety indirectly, like algebraic enums,
       | assignable and movable arrays, tuples built into the language,
       | etc. Generics in Rust, however, are significantly more painful to
       | write due to the lack of duck typing. Perhaps the Rust devs felt
       | that duck typing was too  "dangerous" for templates, but because
       | they're statically evaluated, these issues almost never
       | materialize, and so in practice duck typed templates are almost
       | always superior to Rust's generics.
       | 
       | C++ is definitely more mature and pragmatic as a language, but
       | Rust is probably better for systems programming overall, at least
       | if they fix their goddamn unsafe UB stuff.
       | 
       | As for move semantics, that book isn't for "what you MUST do to
       | keep things semi-safe!" though it contains some stuff to that
       | effect. It's more about optimization and tricks from what I can
       | see. Move semantics take a little while to wrap your head around,
       | but once you know the basics of C++'s move semantics, you can
       | pretty much figure out what's going to happen by looking at a
       | class' definition briefly.
        
       | PaulHoule wrote:
       | I wish it was but I'm afraid it's not.
        
       | extrememacaroni wrote:
       | I dunno if it's doomed or not, but I picked up the language again
       | recently for a project of mine and whenever I look at
       | cppreference.com for any of the newer features I feel like an
       | idiot. Feels like studying for college exams all over again.
       | Smart pointers is as far as I'm gonna go with the post C++03
       | features, miss me with all the template stuff and compiler
       | tetrises.
        
       | forrestthewoods wrote:
       | > Is C++ Doomed
       | 
       | I wish. Unfortunately I think it is we who are doomed and stuck
       | with it forever.
        
       | henning wrote:
       | > Let's say I want to know if a constructor failed
       | 
       | This seems like the beginning of a bad situation. I mean, this
       | shows why constructors that do any complex work that can error
       | out are a bad idea.
        
         | AnimalMuppet wrote:
         | No, they're fundamental to how C++ is idiomatically used.
         | "Resource acquisition is initialization". This means that, if
         | you have an object, and creating that object can fail, then it
         | should fail in the constructor. So you either have a _valid_
         | object, or none at all - you _never_ have an invalid object.
         | 
         | Now, you could squint and say that an optional<T> returned from
         | a factory is a valid object; that is, it's a valid optional<T>.
         | I can sort of twist my mind that far, but it's not idiomatic.
         | (It might become so, I suppose...)
        
           | qayxc wrote:
           | How is a factory function _not_ idiomatic? It 's not as if
           | C++ is a single paradigm language that encourages a singular
           | style or paradigm like OO.
           | 
           | RAII is a mess in certain circumstances (looking at you,
           | Vulkan API) and sometimes straight up just doesn't work
           | anyway.
           | 
           | If your initialisation is prone to failure, don't do it in a
           | ctor.
        
           | henning wrote:
           | This just shows what a bad idea RAII is. You're destroying
           | opportunities for managing resources in bulk and coupling
           | that to just gathering a group of related data under a single
           | identifier.
        
       | pjmlp wrote:
       | Until HPC, HFT, GPGPU, compiler development industries select
       | another workhorse, nope.
        
         | daenz wrote:
         | Don't forget gaming!
        
           | pjmlp wrote:
           | That is part of GPGPU. :)
        
         | mk89 wrote:
         | yeah but it sucks to become the new "cobol" - I hope not, at
         | least, as I am learning now C++ after so many years and I
         | really like it. But hey, I don't make money with it, so I can't
         | talk about how frustrating it must be to debug some weird
         | stuff, etc.
        
       | jvanderbot wrote:
       | C++ is doomed, but it'll add so many features to mimic the cool
       | new things that it'll cease to be C++.
       | 
       | TFA: "That is, of course, until you realise the design philosophy
       | that is being followed. They literally just implement whatever
       | the current fad in programming is."
        
       | posharma wrote:
       | I hear you.
       | 
       | * Move semantics itself requires a whole book
       | https://www.cppmove.com/.
       | 
       | * RVO has always confused me. It was much simpler in the C++03
       | days. Copying large objects by value was inefficient. So you
       | always used references. Period.
       | 
       | * Don't fully agree with the commentary on exceptions; it's the
       | same in most other languages if you want resources to be
       | freed/given back.
        
       | wiineeth wrote:
       | I used to be a C++ Fan Boy. I just started learning rust last
       | week and noticed how old c++ is. All the good practices are built
       | into rust that you need to specifically write in C++.
        
       | Jyaif wrote:
       | > IS C++ DOOMED?
       | 
       | Yes, as are all of today's imperative programming languages.
        
       | leni536 wrote:
       | > I have two options, one is to pass in an in-out parameter, the
       | other is to the throw an exception.
       | 
       | These are not the only options. You can also return `optional<T>`
       | or an other wrapped value from a factory function.
        
       | angelbar wrote:
        
       | hermitdev wrote:
       | "Is C++ Doomed?"
       | 
       | No. The FILE example is either bad, tired, lazy or shows lack of
       | knowledge of the standard library. A better way of
       | opening/closing the FILE with RAII is right in the example [0] of
       | unique_ptr at the wonderful cppreference.com:
       | std::ofstream("demo.txt") << 'x'; // prepare the file to read
       | {             using unique_file_t = std::unique_ptr<std::FILE,
       | decltype(&close_file)>;             unique_file_t
       | fp(std::fopen("demo.txt", "r"), &close_file);             if (fp)
       | std::cout << char(std::fgetc(fp.get())) << '\n';         } //
       | `close_file()` called here (if `fp` is not null)
       | 
       | I'm sorry, I'm not trying to be negative towards the author, but
       | you need a better example than opening/closing FILE if you're
       | going to make a compelling argument about the looming death of
       | C++.
       | 
       | [0] https://en.cppreference.com/w/cpp/memory/unique_ptr
        
         | morelisp wrote:
         | But this also shows a lot of warts that mean RAII isn't "really
         | working", as in it isn't reducing the mental load of resource
         | management as far as we'd like.
         | 
         | - You've needed to manually specify the deleter. Why do I need
         | to invent unique_file_t myself? The stdlib is failing to
         | support idiomatic language use.
         | 
         | - Probably most critically, fp can be null! If A _is_ I, then I
         | _is_ A, yet here we are with a resource in-scope but not
         | allocated!
         | 
         | - You've had to manually add a scope to handle it, and (unlike
         | e.g. Python with or Java try) there's nothing per se to
         | indicate that's what the scope is for. SBRM is supposed to be
         | interesting because of how often our scopes align with
         | lifetimes naturally; manually adding a scope isn't too far from
         | manually calling a destructor.
         | 
         | Please don't respond with the reasons "why" it is this way; we
         | all know. That also doesn't change _that_ they are still major
         | warts that undermine safe, clean C++.
        
           | jcelerier wrote:
           | > - You've needed to manually specify the deleter. Why do I
           | need to invent unique_file_t myself? The stdlib is failing to
           | support idiomatic language use.
           | 
           | But you're not supposed to use fopen / fclose at all, in
           | unique_ptr or not, so the language shouldn't encourage you to
           | use those.
           | 
           | They are just often shown in unique_ptr, because when making
           | examples to teach the language, it's a quick and easy way to
           | showcase how to wrap any kind of pre-existing handles, not
           | only memory, through the unique ownership semantics of
           | unique_ptr; any other example would be platform specific
           | (say, HWND on Win32, GL contexts or who knows what). Everyone
           | knows fopen/fclose.
           | 
           | Basically, that unique_ptr thing is just here to show that
           | it's possible to quickly ease porting 45 million lines of C
           | to C++ without having to change every fopen into an
           | ifstream/ofstream.
        
         | nyanpasu64 wrote:
         | Using a function pointer as a unique_ptr's deleter type, and
         | passing it in at runtime, makes the unique_ptr fat (two
         | pointers large). To avoid this overhead, I prefer passing in a
         | default-constructible type with an operator() deleter function,
         | and not passing in a value into the unique_ptr constructor (htt
         | ps://github.com/nyanpasu64/qvgmsplit/blob/edcce6df391c15b...).
         | A neat party trick is to use the decltype of a lambda (https://
         | old.reddit.com/r/cpp/comments/rlvsq0/does_anybody_re...).
        
         | CyberRabbi wrote:
         | His point is that C code often calls into C++ code. This is
         | always incorrect unless the C++ entry point is guaranteed not
         | to throw an exception. This is especially bad since this is not
         | automatically checked by default.
        
       | bullen wrote:
       | I think the first step is to explain to people that C++ is just C
       | with more stuff, the original is way simpler and the ONLY real
       | reason to use a C++ compiler is that most dependencies that you
       | need (OpenGL, OpenAL etc.) uses C++ by default.
       | 
       | That took me 3 years to figure out on my own, such a waste of
       | time.
       | 
       | In C++ defense namespaces, strings and streams do have some
       | usefulness to them, but if I could use a C compiler to build a
       | modern 3D MMO I would remove those in a heartbeat!
       | 
       | Edit: I know there are construed ways to call the OpenGL API
       | directly without wrapper, but GLEW (damn Windows) is too
       | comfortable for me to switch yet... Eventually I might switch to
       | TinyC and write my own OpenGL/AL... That or implement my own
       | J2ME!
        
         | Jtsummers wrote:
         | OpenGL, at least, is a C API, not C++. If you're using it
         | directly, there is no need to use a C++ compiler. EDIT: And I
         | just double checked, OpenAL is also a C API.
        
         | tubs wrote:
         | OpenGL is a pure C api, absolutely not c++.
        
         | isaiahg wrote:
         | All those things are C APIs not C++
        
       | zwieback wrote:
       | It's true that implementing a standard library in C++ seems
       | daunting but
       | 
       | a) most C++ users don't implement stuff that goes into the
       | standard
       | 
       | b) you can tailor your data structures to the problem at hand
       | instead of making it usable with every type and use case
       | 
       | c) quoting Stroustrup: "There are only two kinds of languages:
       | the ones people complain about and the ones nobody uses".
        
         | sharikous wrote:
         | b is a limitation. C++ makes writing good general code a
         | nightmare so you reduce to taylor your data structures to your
         | problem
         | 
         | c is just folk wisdom. That quote is just untrue
         | 
         | a is true but it has gotten to the point that implementing C++
         | std libraries and compilers is so daunting a task that even the
         | big vendors do not rush into it anymore. There is this growing
         | fatigue that left only MSVC, gcc and llvm in the game, and even
         | them are always late to the standard
        
           | rowls66 wrote:
        
           | Kranar wrote:
           | >c is just folk wisdom. That quote is just untrue
           | 
           | Nothing folk wisdom about it. It's not 100% true but the
           | quote is meant to highlight that most of the languages that
           | get highly praised tend to the ones where the only people
           | using it are those who really love it. People don't use C++
           | or JavaScript or other high demand languages because they're
           | such wonderfully designed languages that they love, they use
           | it whether they want to or not because it's the industry
           | standard. Compare that to people who write Haskell, or D or
           | other niche languages where the only people using those
           | languages are the ones who really like it. If people had to
           | use Haskell or other smaller community languages because they
           | somehow became industry standards, I assure you'd be hearing
           | tons and tons of complaining about performance issues, how
           | buggy they are, how annoying it is to do X, Y, Z...
           | 
           | As is you don't hear people complaining about Haskell or D
           | because the people who find those languages to be annoying
           | simply stop using them and there's little reason to write a
           | blog post about it. The predominant reason to write a blog
           | post about a niche language is to basically give it some
           | degree of praise.
           | 
           | >There is this growing fatigue that left only MSVC, gcc and
           | llvm in the game,
           | 
           | What do you mean by this? I'm not aware of any other language
           | that has three independent implementations that are all kept
           | up to date within a year or so, the only language that comes
           | close would be Java but even that is mostly dominated by
           | Oracle's implementation.
        
           | zwieback wrote:
           | okay, good observation on a
           | 
           | apparently the quote is confirmed by the man himself, though:
           | https://www.stroustrup.com/quotes.html
        
             | ModernMech wrote:
             | I think what the OP meant was that the sentiment behind the
             | quote doesn't reflect reality, not that Stroustrup didn't
             | say it.
        
       | AnimalMuppet wrote:
       | No, just as Betteridge's Law would lead you to suppose.
       | 
       | This really boils down to "creating your own custom data object
       | that plays nice in all scenarios is too hard". (Or, more
       | cynically, too hard for the author - some of us don't find it
       | particularly hard.) Does that mean C++ is doomed? No, it just
       | means that not all C++ developers are going to create objects
       | with full RAII initialization semantics and full rule-of-five
       | acts-like-an-elementary-data-type behavior.
       | 
       | And that's fine! Not every object needs that. And if your object
       | doesn't need that, you don't have to put it in.
       | 
       | The problem, of course, comes when you have an object that
       | _needs_ all that, and you don 't know how to do it. But that's
       | kind of true for _every_ feature you don 't know how to use, in
       | _every_ language. So are all languages doomed? No.
       | 
       | Now, you could make a case that creating objects that have all
       | those behaviors is the ideal in C++, and it's too hard to achieve
       | that ideal. But hyperventilating about how that makes C++ doomed
       | doesn't do anything to actually make that case.
        
       | gigel82 wrote:
       | No
        
       | isaiahg wrote:
       | I believe it's inevitable. But I would have given a very
       | different answer even a few years ago. What strikes me the most
       | though is that C++ is that it's not being succeeded by a new
       | better C++ but instead it's being slowly replaced by multiple
       | languages at once. C#, Java, Go, Rust, Swift, and JavaScript,
       | (and maybe even python) are all replacing C++.
       | 
       | In 22 if someone wants to make a general desktop app they'll
       | probably use web technologies, or python, or java. If you do
       | server development it's Python, C#, Java, Ruby, or Go. Embedded
       | development is predominantly pure C. C++ is used but less often.
       | Gaming is still a singular C++ affair but I'm starting to see C#
       | make headway and now Rust is gaining traction. I've seen more
       | game developers excited about Rust than I have about anything in
       | the last 5 years.
       | 
       | You can see it happening slowly, as more and more technologies
       | are coming out and chipping away at it's use cases. Unless you're
       | targeting game consoles, or a very specific rare domain, using
       | C++ might be more of a handicap with so much better options out
       | there.
        
         | jcelerier wrote:
         | > In 22 if someone wants to make a general desktop app they'll
         | probably use web technologies, or python, or java.
         | 
         | I wonder what apps you are talking about. The immense majority
         | of apps I use are Qt. Just last week I was trying some newish
         | Intel GPU profiler GUI:
         | https://www.intel.com/content/www/us/en/developer/tools/grap...
         | ; a Qt app. Last month I produced a video, with Da Vinci
         | Resolve, also a Qt-based software.
         | 
         | On the other hand, on my whole desktop I literally don't have
         | any Java apps installed at the moment, like, not even a JDK.
         | There may be a 2012-era copy of Minecraft in some backup
         | folder. Likewise for Python, at most some apps will embark
         | Python scripting around a C++ core like Blender but I don't
         | think I use any app where the main() is written in Python.
        
       | cenny wrote:
       | I'm no C++ expert. But Wouldn't a private constructor and a
       | static method that returns nil if input is bad fix this problem?
       | 
       | Joshua Kerievsky talks a bout this problem in his book
       | [Refactoring to
       | patterns](https://www.programmingbooks.dev/#refactoring-to-
       | patterns), specifically the refactoring "Replace Constructors
       | with Creation Methods"
       | 
       | That is maybe beside the point of the articles argument he is
       | making about C++ being bloated and hard to use and read
       | -\\_(tsu)_/-
        
         | brigade wrote:
         | Then you can't allocate the object in-place (e.g. on the
         | stack), and also you have to ban copy assignment/construction.
         | Probably a more common solution is like what STL's own fstream
         | does - don't throw an exception on construction, and have a
         | "not valid" state.
        
       | titzer wrote:
       | Something that I have learned is that we build abstractions to
       | hide complexity. In so far as abstractions are not leaky and are
       | fast, all is good. The moment you have a bug, or the moment you
       | want to analyze performance, abstractions absolutely puke their
       | f'ing guts at you and all that smart stuff someone thought no one
       | would ever see spills all over the floor and it's a flat-out
       | mess. C++'s pervasive unsafety and pervasive obsession with
       | performance are the worst kind of witch's brew for plunging you
       | straight into a hell of unimaginable depths at the slightest
       | mistake.
       | 
       | In particular, undefined behavior is so pernicious that it
       | exposes literally everything about the machine and the entire
       | program at once--its memory layout, compiler optimizations, and
       | whatever else you weren't supposed to see. You are staring at a
       | whole pile of hot garbage. There are literally no constraints on
       | what the entire system is required to provide to you as aid to
       | debug the problem. Undefined behavior literally gives _no
       | meaning_ to the entire execution of the program since its start,
       | no matter where or when the undefined behavior occurred. There is
       | no  "before" UB and "after" UB--the entire execution is invalid.
       | You are in a parallel universe that is not required to make
       | sense.
        
       | coliveira wrote:
       | C++ has its problems, but it has two advantages to other
       | languages: (1) it is evolving and extensible at a deep level: one
       | can identify strategies and create solutions based on the
       | template system, because that's how the standard library itself
       | is created. (2) it is backward-compatible, which means that we
       | can continue to support the incredible amount of software already
       | available for C and C++.
        
         | johannes1234321 wrote:
         | While that is true, this in turn means that the result is
         | complex. You have old and new ways to do things, and new things
         | are complex with all the integrations into the different parts.
         | 
         | Unified initialization sounds great, always use brace
         | initialisation and be done, you think ... but no it's complex.
         | 
         | Or move semantics and universal(?) references are introduced to
         | solve efficiency problems and make things nicer. For simple
         | stuff it works, but oh the mess becomes wild.
         | 
         | How well modules will work out is still to be seen. Suddenly
         | you need the build system to be a compiler and can't parallel
         | build everything anymore, but have to create a dependency graph
         | to build in order, while the standard treats that as a
         | implementation detail.
         | 
         | C++ has some great features, however it would need a big
         | cleanup, a cleanup which would break all code, worse than
         | Python 2 vs. Python 3, in a world where other environments and
         | languages are viable alternatives. Thus C++ can't afford the
         | big jump.
        
       | dathinab wrote:
       | Both C++ and Rust are rather complex.
       | 
       | But in Rust the negative effects of the complexity are mostly
       | contained by the compiler (i.e. if you get it wrong you get a
       | compiler error).
       | 
       | In C++ it on the other side can easily lead to UB and in turn to
       | potential security vulnerabilities.
       | 
       | So IMHO today there are _very_ few cases where C++ is still the
       | appropriate choice.
       | 
       | Just to be clear I'm not saying rust did replace C++, but there
       | is a wide variety of languages which are a better choice for many
       | use-cases which once where filled by C++. Rust just further
       | reduces use-cases where I would say C++ is appropriate.
       | 
       | But it's anyway not Doomed as there are just way to many existing
       | projects. Not just legacy projects, but existing still maintained
       | for many years projects.
        
       | lazypenguin wrote:
       | On a long enough timeline I do believe C++ is doomed or will
       | otherwise become something completely unrecognizable from its
       | current form. Similar to how C++98 code is wildly different than
       | modern C++ code. People often ask, "is C++ a good language?" or
       | "is XXX better than C++?". Doesn't really matter, what matters is
       | whether people are willing to pay the "tax" of
       | learning/using/maintaining C++. C++ is a generalist language and
       | new languages are competing with it by specializing in different
       | areas. If I am going to use something as unergonomic and perilous
       | as C++ it needs to be for some advantage but looking into the
       | future and comparing to newer language, that advantage is
       | becoming less clear.
        
         | soup10 wrote:
         | C++ is not a generalist language, its strengths are high
         | performance, direct memory access and native compilation. If an
         | application doesn't need any of that, a higher level language
         | is usually better.
        
       | tehjoker wrote:
       | The commentary is good, but the conclusion is unsupported. These
       | are annoying issues, but doom they do not make.
        
         | quantified wrote:
         | Define "doom" or "death". Cobol still runs, FORTRAN still runs,
         | not the coolest in the TIOBE index but in the tail. A mammalian
         | body doesn't die all at once, either, but past some threshold
         | of vitality does definitively die and remains dead. Really
         | unclear whether these languages will be dead until the last
         | compiler stops compiling. So the discussion needs a definition
         | for the threshold the language may reach.
        
           | tehjoker wrote:
           | I think "dead" is pretty clear. The work is not used anymore
           | at all, or only for hobbyist projects.
           | 
           | I think "dethroned" or "unpopular" would be more correct
           | presuming that C++ would lose popularity over time due to the
           | issues raised.
        
         | nyanpasu64 wrote:
         | In my C++ code, I've copied Rust and expressed fallible
         | initialization by returning a std::optional<T> from a static
         | method (which the article failed to mention). The problem is
         | that (like Rust) you lose placement initialization, and (unlike
         | C++ _or_ Rust) you can 't initialize private fields using
         | aggregate initialization or initializer lists, and must write a
         | passthrough constructor (which can't even be private because it
         | breaks make_unique).
         | 
         | https://github.com/hsutter/708 is a C++ proposal which unifies
         | placement constructors and writable out-parameters ("definite
         | first use"). I don't _think_ it makes placement initialization
         | fallible, but I 'm not sure.
        
         | mark-r wrote:
         | Have you heard the phrase "death by 1000 cuts"? That's what
         | programming in modern C++ is like. I agree they don't quite
         | make the case for "doom" but it's still not a pretty picture.
        
           | qayxc wrote:
           | This image is mostly painted by people who forgot to stop.
           | 
           | By that I mean stop using every feature and detail because
           | it's there.
           | 
           | C++ can be used like a much better C (smart pointers,
           | references, const-correctness, generics via templates) and
           | most of the complications can be ignored that way.
           | 
           | Premature optimisation is another big factor. If there's no
           | measurable performance bottleneck, things like move-semantics
           | don't even matter in practise for example.
        
       | zibzab wrote:
       | Having followed SerenityOS for some weeks, I now believe C++ is
       | doing fine.
       | 
       | Also, that cookie pop-up on WordPress gave me cancer.
        
       | Const-me wrote:
       | > I have two options, one is to pass in an in-out parameter, the
       | other is to the throw an exception.
       | 
       | Another one would be not using constructors at all, instead
       | implement initialize() or create() method in the class.
       | 
       | > Now you have the issue that every single operation can throw an
       | exception
       | 
       | Depends on the code. I tend to avoid exceptions when I can.
       | 
       | > you need to write code that wraps every possible resource in
       | RAII logic.
       | 
       | That's generally a good idea regardless of whether exceptions are
       | used or not. Without RAII for file handles, next day/month/year
       | another programmer will write `if(condition) return;` and leak
       | the handle.
       | 
       | > And when it comes to implementing move and copy constructors
       | you are left feeling like you've definitely done something wrong
       | 
       | For many classes in my code, I disable both with =delete; copy
       | constructor. This makes a class which can be neither moved nor
       | copied, which simplifies things substantially. For instance, if
       | you need a RAII equivalent of CAtlFile [0] over FILE* from
       | stdio.h, immovable file handles might be a good idea. Another
       | thing, if the class owns many gigabytes of data in some
       | collections, you wouldn't want to copy anyway, too expensive. In
       | some cases I want to move such objects, for that I define a
       | swap() method. An explicit swap() method is IMO more readable
       | than std::move.
       | 
       | For many other classes in my code, I declare no constructors at
       | all because the default compiler-generated ones already doing
       | what I want.
       | 
       | [0] https://docs.microsoft.com/en-
       | us/cpp/atl/reference/catlfile-...
        
       | Bigpet wrote:
       | To the point of complexity, the allocation makes the container
       | unfit for over-aligned types. But honestly I don't even know if
       | STL containers are over-alignment aware.
       | 
       | So instead of                 (T*)malloc(sizeof(T) * capacity_);
       | 
       | it should be something like this if you want to deal with over-
       | aligned types.                 (T*) operator
       | new(sizeof(T)*capacity_, std::align_val_t{alignof(T)});
       | 
       | Also, in the reallocation function the elements are move-assigned
       | to uninitialized memory. They should be move-constructed with a
       | placement-new. And of course calling the destructors of the
       | moved-from objects is still required.
       | 
       | It's of course a little telling that the compiler will not
       | complain about these errors and you have to use and know to use
       | tools like address sanitizer, memory sanatizer and valgrind to
       | let a machine help you find them.
        
       | Ekaros wrote:
       | Define doomed? Not being around in century? It certainly have
       | enough inertia to carry on for long time, probably decades.
        
       | qayxc wrote:
       | I am very confused.
       | 
       | Some of these ramblings are just plain wrong, like
       | 
       | > But exceptions come at a performance cost.
       | 
       | No they don't. An exception that isn't thrown costs nothing.
       | 
       | Also
       | 
       | > Let's say I want to know if a constructor failed. I have two
       | options, one is to pass in an in-out parameter, the other is to
       | the throw an exception.
       | 
       | Using a factory function instead is a third option that avoids
       | this. Besides, this is a false dichotomy to begin with - it's
       | perfectly acceptable to simply record the validity as part of the
       | object's state and provide access to that (especially if the
       | state established in the ctor isn't an invariant). So there's
       | more than just two options.
       | 
       | Then there's more confusing arguments like the custom FILE
       | wrapper. So instead of just using std::unique_ptr<std::FILE,
       | decltype(&close_file)>, i.e. the idiomatic solution for the past
       | decade that the author claims to be after, they blame their
       | C++03-style on the language? I don't understand.
       | 
       | Fortunately they don't provide any concrete examples of "features
       | bleeding into each other", since I suspect that would only
       | further illustrate their lack of understanding and knowledge
       | gaps...
       | 
       | They end with
       | 
       | > I'm not in the business of writing the perfect program that
       | satisfies an arbitrary standard. I'm in the business of making
       | stuff with tools. [...]
       | 
       | Well great, then don't use C++ if you're not productive with it.
       | Yes C++ is a complex language. A big portion of its complexity
       | can be avoided by simply not using features one doesn't need (or
       | understand) or by limiting oneself to a reasonable subset of said
       | features.
       | 
       | The author seems to look for something like Go instead. C++
       | wasn't exactly "designed" and is the result of a steady evolution
       | that kept some legacy baggage and bad earlier decisions around.
        
         | ghosty141 wrote:
         | To add to "Let's say I want to know if a constructor failed."
         | 
         | Constructors are simply not meant to fail. You should never do
         | something like io in a constructor. For everything more
         | complicated than setting some fields a factory is better
         | suited.
        
           | nmilo wrote:
           | Why? Isn't one of the ideas of C++ that every constructed
           | object is in a valid state? Even the STL has constructors
           | that can fail, and it does it through member variables/flags,
           | which doesn't seem like a very good solution:
           | std::fstream s(filename);         if (!s.is_open()) {
           | std::cout << "failed to open " << filename << '\n';         }
           | 
           | No one should ever have to use factories, they really suck.
           | Too much boilerplate, too much code duplication.
        
             | jcelerier wrote:
             | how do you do non-trivial dynamically-loaded plug-ins
             | without factories ?
        
           | dataflow wrote:
           | > Constructors are simply not meant to fail. You should never
           | do something like io in a constructor. For everything more
           | complicated than setting some fields a factory is better
           | suited.
           | 
           | How would you reconcile that with the fact that
           | std::vector<T>::vector() is explicitly permitted to fail? [1]
           | Do you construct your std::vector<T> objects with factories
           | too?
           | 
           | > Exceptions: _Calls to Allocator::allocate may throw._
           | 
           | [1] https://en.cppreference.com/w/cpp/container/vector/vector
        
         | knorker wrote:
         | > The author seems to look for something like Go instead
         | 
         | But in Go you also have to write exception safe code, something
         | the author complained about.
         | 
         | Before someone says "but you're not supposed to throw or catch
         | in Go": the std http server swallows exceptions thrown in
         | handlers, and fmt.Print does for String callbacks too.
         | 
         | And before someone says Go doesn't have exceptions: It does,
         | 100%, in all but name.
         | 
         | I actually find C++ to be way more designed, though through,
         | and consistent than Go.
         | 
         | That's not to say that Go isn't a vastly simpler language. It
         | is. And C++ has way more "just because you can doesn't mean you
         | should".
        
           | nmilo wrote:
           | > And before someone says Go doesn't have exceptions: It
           | does, 100%, in all but name.
           | 
           | But here the name actually matters. They're called "panic"s,
           | not exceptions. When you panic, you signal a fatal error that
           | should ideally terminate the program. You shouldn't care
           | about closing files or freeing memory, because well-written
           | Go does not panic, and if it does, execution will end very
           | soon. A panic in a String() method is just bad code, don't do
           | it. Meanwhile in C++ you can reasonable expect an exception
           | anywhere. Bad arguments, invalid states, IO failures, and
           | other errors that should be expected in the execution of a
           | program all throw exceptions in idiomatic C++.
        
         | Rusky wrote:
         | > An exception that isn't thrown costs nothing.
         | 
         | It costs binary size for unwinding tables, and in some cases it
         | is an optimization barrier.
        
           | jcelerier wrote:
           | and yet when benchmarking, exceptions are often faster.
           | cross-posting a comment I just made on r/programming: https:/
           | /www.reddit.com/r/programming/comments/sz86fl/comment...
        
         | UncleMeat wrote:
         | C++ exceptions aren't totally zero overhead. For an obvious
         | example, you pay in code size.
         | 
         | I do agree with the overall analysis, though.
        
       | CyberRabbi wrote:
       | > That means you need to write code that wraps every possible
       | resource in RAII logic.
       | 
       | This is a great point. It means that you cannot mix C and C++
       | code without care, which seems like a huge design smell to me
       | especially since this is not something that can easily be
       | verified statically.
       | 
       | Using C++ with exceptions disabled is the only foolproof way to
       | run it in a large heterogenous application.
        
         | AlexandrB wrote:
         | > Using C++ with exceptions disabled is the only foolproof way
         | to run it in a large heterogenous application.
         | 
         | Is there a way for a ctor to report failure in newer C++
         | standards or are exceptions still the only way[1]?
         | 
         | [1] https://www.yosefk.com/c++fqa/exceptions.html#fqa-17.2
        
       | frozenport wrote:
       | Practical implementations of data structures require
       | consideration of memory layouts and pools which have real
       | equivalent in other languages.
       | 
       | For example, imagine building a graph structure to hold a uint8
       | field but calling malloc for every node. You get that and worse
       | in Python.
       | 
       | Simply put you cannot write performant, non-toy data structures
       | outside of low level languages like C++.
       | 
       | I think OP is simply not aware of the kind of structures that are
       | used in algorithm critical applications, like CAD, compilers,
       | etc.
        
       | nine_k wrote:
       | C++ is as doomed as Fortran.
       | 
       | Even if you look at it as at a strictly legacy technology, this
       | legacy will be around for a _looong_ time. A lot of important
       | code is written in C++, without any viable replacement.
       | 
       | Also, like Fortran, it will keep improving even while relegated
       | to its niche, and will become much nicer (yes, even nicer than
       | the latest standard).
        
       | ridiculous_fish wrote:
       | OP is trying to create a circular queue by using malloc(). This
       | may be a good learning exercise, but the code could be made much
       | simpler by just wrapping `std::vector`. Working with raw
       | uninitialized memory is difficult in any language.
       | 
       | Most of the article was about C++ exceptions specifically, and I
       | agree C++ exceptions are probably "doomed." They are already
       | disfavored by major codebases (Google, LLVM, others). It is
       | indeed unreasonably hard to make exception-safe C++ containers.
       | 
       | One possible future path here is Herb Sutter's Zero Overhead
       | Deterministic Exceptions [1] which "throws" values through the
       | ordinary return path, as is done in Swift/Rust/Go etc.
       | 
       | I don't know how `bad_alloc` will get handled, but I'm not sure
       | how useful that even is any more, given Linux memory overcommit.
       | Curious to know if anyone takes bad_alloc seriously?
       | 
       | > Let's say I want to know if a constructor failed
       | 
       | Usual advice here is "don't write fallible constructors." Use a
       | factory function instead.
       | 
       | I can't agree with some of the other points. I don't see how
       | "copies that result from calling a function have a different
       | meaning to regular copies." RVO became guaranteed in C++11, and
       | you don't need to worry about value categories when implementing
       | move or copy constructors. C++ is not an easy language but it
       | does have rules. And most of the time this stuff can be ignored:
       | if I copy a string an extra time it usually doesn't matter.
       | 
       | 1: http://www.open-
       | std.org/jtc1/sc22/wg21/docs/papers/2018/p070...
        
       | reincarnate0x14 wrote:
       | I hope so, because I irrationally decided I didn't like it versus
       | Objective-C a long time ago and I can't wait to be vindicated :)
       | 
       | But even if it is doomed, given inertia it will still be a
       | mainstay of HPC and such long after Rust or whatever takes the
       | crown as the language people take the time to complain about, to
       | paraphrase Stroustrup.
        
       | iamflimflam1 wrote:
       | You'll have to prize C and C++ from the cold dead hands of the
       | embedded programmers. Most of them are still doing OO using plain
       | C.
        
         | nine_k wrote:
         | C, yes! C++?
         | 
         | I also think that Zig has a good chance to become popular in
         | the MCU space.
        
           | colejohnson66 wrote:
           | The Arduino and friends can run on C++. The Arduino "IDE"
           | uses "C _almost_ ++", but C++ compilers for AVR and ARM
           | boards do exist.
        
       | bodhiandphysics wrote:
       | I should note that writing data structures in rust is also a
       | fraught business! In practice, that's just a property of
       | languages that don't have garbage collection.
       | 
       | Exception safety is a serious complication in c++! Exceptions
       | were a mistake in the language, particularly given the existence
       | now of std::optional.
        
         | bodhiandphysics wrote:
         | Finanally, c++ is the only language I would ever want to do
         | linear algebra in, or for that matter, many graph algorithms.
         | Rust is adding features to make it useful for linalg, but it's
         | not their yet. Linear algebra is important!
        
           | chrisseaton wrote:
           | > Finanally, c++ is the only language I would ever want to do
           | linear algebra in
           | 
           | Isn't something like MATLAB better for linear algebra than
           | C++?
        
             | Jtsummers wrote:
             | Or Fortran or Julia.
        
               | bodhiandphysics wrote:
               | I don't want to use Fortran! Julia is lovely
        
             | bodhiandphysics wrote:
             | Low level linalg... implementing solvers using openmp and
             | CUDA
        
               | [deleted]
        
         | beached_whale wrote:
         | that's just false. Exceptions were not a mistake and
         | std::optional isn't even a good type to replace more than
         | communicating that there is no value. It's as good as returning
         | nullptr, in this case. And what strength of an exception
         | guarantee does one really need? A basic guarantee is not that
         | bad to give and most things do not need to worry if they don't
         | use unmanaged resources without RAII types(e.g pointers).
         | 
         | But the big thing is, a large amount of code does not have
         | error states at all, they always work. Another chunk have
         | preconditions, that are generally developer errors, that
         | shouldn't be throwing either. That leaves the parts that
         | interact externally. And here is where one learns, it's not a
         | dichotomy of exceptions or error flags/results... it can be
         | both. It's more to do with the shape of the code.
         | 
         | One example is, one is passing a string buffer to a parser, an
         | exception is probably fine as the error cannot be dealt with
         | within the parser. It didn't create the malformed document and
         | cannot fix it. To use error types will greatly complicate the
         | inner workings of the library as it has to deal with errors at
         | a majority of calls internally. This leads to more
         | bugs(assuming more code == more bugs) and complexity/branches.
         | One already paid for the branch to check for the error in the
         | document too, now they have to pay for branching up the stack.
         | Exceptions strong part is that they cannot be implicitly
         | ignored. One can do that with error types that force a read
         | prior to destruction too though.
         | 
         | For short distance things, where one is handling the error
         | state after the call, not throwing is fine.
        
         | edflsafoiewq wrote:
         | Rust has the same exception safety issues. Eg Vec::retain_mut:
         | https://github.com/rust-lang/rust/blob/532d3cda90b8a729cd982...
        
       ___________________________________________________________________
       (page generated 2022-02-23 23:01 UTC)