[HN Gopher] C++23: The Next C++ Standard
___________________________________________________________________
C++23: The Next C++ Standard
Author : ibobev
Score : 163 points
Date : 2023-07-10 09:31 UTC (1 days ago)
(HTM) web link (modernescpp.com)
(TXT) w3m dump (modernescpp.com)
| IAmLiterallyAB wrote:
| Hoping compilers will get their C++20 modules implementation
| working well enough that we'll get C++23 standard library modules
| soon. As an outside observer, it seems like progress is happening
| in spurts, but it feels kinda slow.
| pjmlp wrote:
| Yes, VC++ is still the only usable one.
| badpun wrote:
| Last time I checked (which was roughly a year ago), even a
| toy project with modules slowed down IntelliSense to the
| point of it being unusable and having to be disabled. Do you
| know if it's better now?
| gerash wrote:
| I'm excited for more usability coming to C++. It's still far from
| the writability of Python. At the same time the Mojo language is
| going the other way: creating a system language out of Python.
| [deleted]
| optimalsolver wrote:
| Just happy to have std::format.
|
| Can't believe I used to use a third-party library ({fmt}) to do
| string interpolation like some kind of caveman.
| whobre wrote:
| Still at c++ 98, but it's always nice to see what other people
| have...
| afavour wrote:
| I've always been aware of C++ (obviously!) but it seemed
| impenetrable to me coming from my experience with C# and
| JavaScript. So I was really pleasantly surprised when I tried it
| out a while ago using the C++20 standard: it felt entirely
| usable! But then I tried Rust, which felt like that plus a pile
| of even better stuff. It's been difficult to find a reason to go
| back since but I'm glad to see even more progress is being made.
| TillE wrote:
| There are a zillion reasons that C++ is still widely used
| (existing software, brand new software that relies heavily on
| existing libraries / game engines), so it's really nice to have
| lots of helpful features being added to the language and the
| standard library.
|
| I'm ambivalent about Rust, but its best feature compared to C++
| is a universal package manager and build system. I like vcpkg
| well enough, but it's not Cargo, it can't be.
| perth wrote:
| The thing that really ticks me off about RUST is it has
| compiler optimizations that can't be turned off. In C++, you
| can typically turn off all optimizations, including
| optimizing away unused variables, in most compilers. In RUST
| it's part of the language specification that you cannot have
| an unused variable, which, for me kills the language for me
| since I like having unused variables for debug while I'm
| stepping through the code.
| fruffy wrote:
| Related: https://xkcd.com/1172/
| coldtea wrote:
| > _which, for me kills the language for me since I like
| having unused variables for debug while I'm stepping
| through the code._
|
| If they're unused, then they're not helping with debugging
| either...
|
| It's quite a bizarre reason
| Buttons840 wrote:
| Not true. Their debugger might display the value of the
| variables, which are unused outside the debugger. There
| are other options in Rust though, like logging. Create
| your "unused variable", and then use it in a debug log or
| dbg macro, etc.
| groos wrote:
| v = function_call(); // inspect v in debugger but it's
| 'unused' to the compiler.
|
| At least this is how it works in C and C++.
| cesarb wrote:
| > In RUST it's part of the language specification that you
| cannot have an unused variable
|
| I believe you're confusing Rust with Go. In Rust, an unused
| variable is just a warning, unless you explicitly asked the
| compiler to turn that warning (or all warnings) into an
| error.
| dralley wrote:
| This is just wrong? You can absolutely have unused
| variables in Rust. It's a compiler warning, not an error.
|
| What you cannot have, is _uninitialized_ variables which
| are used.
| justinpombrio wrote:
| If your development style demands _uninitialized_
| variables, you can set them to `= unimplemented!()`
| during development.
|
| And as someone else said, if all you want is _unused_
| variables without warnings, you can say at the top of the
| root of the crate (`main.rs` or `lib.rs`):
| #![allow(dead_code)]
| vacuity wrote:
| There isn't a Rust language specification, and you can use
| #![allow(dead_code)]
|
| at the root of the crate
|
| I'm not sure what you mean by "optimizing away unused
| variables", so I'm interpreting it as variables that are
| literally unused after declaration.
| IceSentry wrote:
| Unused variables just generate warnings. It will still
| compile.
| [deleted]
| klysm wrote:
| Also like memory safety
| pjmlp wrote:
| Latest VS has AI integration with the static analyser, I am
| yet to try it out.
|
| If we get a C++ Copilot as "borrow checker" that is already
| quite an improvement, even if not perfect.
| coldtea wrote:
| What if Rust gets a Copilot to make working with borrow
| checker trivial?
|
| Hmm, another idea: what if a language's compiler itself
| use an LLM to find invariants and issues (like race
| conditions) in the code?
| pjmlp wrote:
| That would be even better, naturally there still exists
| the whole ecosystem issue.
|
| In any case, taken to the extreme, the language won't
| matter any longer, beyond some druids with the knowledge
| to implement Star Trek like computing models.
|
| We will be left to architect roles.
| fassssst wrote:
| The reason is not the language itself but what it lets you do.
| E.g., if you want to use Unreal Engine or write an audio
| plugin, learn C++ for that.
|
| If you want to mess around with machine learning, learn enough
| Python for it.
|
| Id you want to make a mobile app, learn Swift or Kotlin.
| seabrookmx wrote:
| That should be the case for every language.
|
| I get that this crowd will nerd out on language specifics..
| but at the end of the day if we're good engineers we should
| use the best tool for the job.
| Conscat wrote:
| The tradeoff is C++ is an amazing language for building a type
| system and zero overhead abstractions, where Rust is extremely
| limiting in that area (orphan rule, constant evaluating const-
| generics, no implicit pedagogically equivalent type
| conversions, users don't define what move semantics mean,
| terrible variadic programming, macros can't interact with CTFE
| or generics, functions/lambdas cannot be const-generics). Some
| of that will probably improve over time, though, although
| metaprogramming in Rust advances slower than C++ since 2014 and
| it has to play catch up already.
| snitty wrote:
| I learned C++ in the late 90s and didn't touch it until
| recently and all the new stuff basically melted my brain. It
| feels like a different language now.
| pjmlp wrote:
| Just like any other language.
|
| Users of 1996's Java won't recognise Java 21, or 2001's C#
| versus C# 12.
| gumby wrote:
| Indeed. I was working in/on C++ back in the late 80s and the
| 90s and I decided a few years ago to do a project with it.
|
| I took the approach of learning it as a brand new language
| that I knew nothing about, and especially avoiding thinking
| about what I already knew about C++ and especially C. The
| result was a language that yes, is a pleasure to work in.
|
| Of course some context could not be ignored: there are about
| six ways to do many things because of back compatibility. But
| that back compatibility is why so many people use it. I write
| in one style, and just know the others for reading other
| people's code.
| zwieback wrote:
| Yeah, that's me, I used C++ professionally throughout the
| 90s and early 2000s, then switched to C# (and C for
| embedded). Unfortunately, the C++ code bases I work with
| are equally old so I also have to look for a brand-new
| project to re-learn modern C++.
| afavour wrote:
| As someone who learned JavaScript in the late 90s I feel the
| same way sometimes! If I'd gone away from the ecosystem and
| returned recently I think it would feel extremely alien.
| nvy wrote:
| For people who are proficient in other languages but haven't
| touched C++ since CS036 in undergrad, some 15+ years ago, what
| would be the best way to learn what "modern C++", both the code
| and the dev stack, looks like?
|
| Asking for a friend.
| mauvia wrote:
| I don't really learn stuff in a structured way so this might
| not be helpful at all, but a youtube walk got me into watching
| CPPCon talks (https://www.youtube.com/@CppCon) and I found them
| generally useful for getting an idea of what's going on in C++
| and what all the magic words to research are. When a bunch of
| people talk about weird gibberish like SFINAE it becomes easy
| to figure out it's something you should search for on wikipedia
| (https://en.wikipedia.org/wiki/SFINAE). note: SFINAE is simply
| a way to make overloading work better by failing gracefully if
| an overload fails to compile.
|
| There's a series of talks called Back to Basics that seems to
| have quite a few talks every year where they discuss C++
| features in general like move semantics or how testing works,
| etc. There have also been talks from the creators of CMake or
| the guys working on the recently added ranges proposal, so it
| does cover tooling as well.
|
| I also quite enjoy following Jason Turner's C++ weekly series
| (https://www.youtube.com/@cppweekly) which also has quite a few
| episodes that are dedicated to new C++ features or new ways of
| handling old problems made available by the new features.
| They're generally quite short, each episode on average is 12
| minutes.
|
| Just looking down the list of videos I see this is also kind of
| a direct response to your question, from only 8 months ago.
| https://youtu.be/VpqwCDSfgz0 [ C++ Weekly - Ep 348 - A Modern
| C++ Quick Start Tutorial - 90 Topics in 20 Minutes ]
|
| For experimenting:
|
| https://gcc.godbolt.org/ is a tool called compiler explorer,
| which is a really good place to experiment with toy code.
|
| It's a site where you can write some code and let it execute,
| to see the results, as well as see what ASM it compiles down to
| for various compilers.
|
| That last feature really helped me figure out whether the
| compiler really does pick up an optimisation I'm trying out.
| (and it's how I got really impressed by how powerful constexpr
| is (that's one of the new keywords))
|
| For References:
|
| Generally the https://en.cppreference.com site is a really well
| maintained wiki that has good explanations of every concept and
| standard library feature.
|
| It sticks to the standard and has use examples and is heavily
| interlinked, as well as some concept pages to give an overview
| of certain topics like overloading, templates, etc. (they also
| have a SFINAE article
| https://en.cppreference.com/w/cpp/language/sfinae)
| htfy96 wrote:
| A Tour of C++ for syntax, https://www.cpp-lang.net/tools/ for
| dev tools
| nanidin wrote:
| I'm a little 10 years out from writing C++ professionally and I
| found this cheat sheet[0] useful. Basically if you have an
| inkling of the concept you're looking for, just search on that
| cheat sheet to find the relevant new C++ thing. Specifically
| for me, we used Boost for smart pointers which are now part of
| the stdlib, and threads are now part of the stdlib as well.
|
| [0] https://github.com/AnthonyCalandra/modern-cpp-features
| r2vcap wrote:
| s/absl::AnyCallable/std::move_only_function/
| jonathankoren wrote:
| Genuinely surprised and confused by "print" and "println"
| ksherlock wrote:
| Take your pick:
|
| print("{:#04x}\n", 0xbeef);
|
| - or -
|
| cout << std::setw(4) << std::setfill('0') << std::hex << 0xbeef
| << std::endl;
| jordigh wrote:
| Yeah, they make sense, just surprising that C++ is finally
| tacitly admitting that iostreams were a mistake.
|
| I kind of wonder why anyone thought iostreams would be a good
| idea to begin with. I don't think anyone but C++ ever created
| a comparable interface.
| q845712 wrote:
| I could be wrong as I was young and not yet in the field,
| but my impression has always been that sometime in the
| 80s/90s as the whole "networking, world wide web, wowie!"
| moment happened, there was this idea that "maybe on a local
| computer everything is files, but on the network everything
| is streams. Hey, maybe everything is streams!?" and C++
| just happened to be creating itself in that zeitgeist,
| trying to look modern and future-thinking, so somebody
| decided to see what would happen if all the i/o was
| "stream-native".
|
| IDK, it'll probably make more sense in another 15 years as
| we clear away the cruft of all the things that tried to
| bring "cloud native" paradigms into a space where they
| didn't really fit...
| GuB-42 wrote:
| I think it is more simple and technical that that.
|
| The big thing is that they wanted type safe IO. Not like
| printf where you can print an integer with %s and the
| compiler won't have a problem with that, and it will
| crash.
|
| Reusing bit shift operators for IO is quite clever
| actually. If you have operator overloading, you have type
| safe IO for free. Remember C++ came out in the 80s as a
| superset of C, these technical considerations mattered.
| std::println doesn't look like much, but it actually
| involves quite significant metaprogramming magic to work
| as intended, which is why it took so long to appear.
| tialaramex wrote:
| > Reusing bit shift operators for IO is quite clever
| actually
|
| It's a miserable trap. Operators should do something in
| particular, because of the Principle of Least Surprise.
| The reader who sees A + B should be assured we're adding
| A and B together, maybe they're matrices, or 3D volumes,
| or something quite different, but they must be things
| you'd add together or else that's not a sensible
| operation for the + operator.
|
| When you don't obey this requirement, the precedence will
| bite you as, as it does for streams. Because you forgot,
| these operations still get bit shift precedence even
| though you're thinking of this as format streaming it's
| just a bit shift and happens when you'd do a bit shift...
|
| Streams looks like something dumb you'd do to show off
| your new operator overloading feature, because that is in
| fact what it is. It should have existed like Mara's
| whichever_compiles! macro for Rust - as a live fire
| warning, "Ha, this is possible but for God's sake never
| use it" - but instead it was adopted for the C++ standard
| library.
| Conscat wrote:
| People have investigated better solutions than iostreams
| for decades. But creating a near perfect interface is
| difficult in C++ because you have almost infinite control
| over the compile time and runtime characteristics of it. It
| has to compile fast, it has to be extensible (even the
| formatting syntax is interchangeable and programmable
| here), it has to give formatting errors at compile time,
| and it has to run nearly optimally. Victor Zverovich
| cracked the code, but only after Meta, Boost, and many
| others put tons of work into their own attempts. Imo,
| std::format is the most complex part of the standard
| library so far in terms of its implementation.
|
| Iostream predates parameter packs, which is why they use <<
| for a variadic API.
| munificent wrote:
| iostreams required only early C++ features and in return
| gave you:
|
| 1. Type safe string formatting. You'd get a compile error
| if the value you tried to write didn't support it.
|
| 2. User-defined formatting support for user defined types.
| You could make instances of your own class support being
| written directly to a stream. Sort of like how some other
| languages let you override `toString()`.
|
| 3. No runtime dispatch for #2. Unlike Java, C#, etc. the
| user-defined formatting code for your type is called
| _statically_ with no overhead of virtual dispatch.
|
| Using operator overloading to achieve all of those was
| _really_ clever and led to a very powerful, efficient, and
| flexible system. It 's also just unfortunately pretty
| verbose and kind of weird.
| 1024core wrote:
| > Genuinely surprised and confused by "print" and "println"
|
| Just like Pascal/Modula-2, except 30 years later :-D
| bluedino wrote:
| Wasn't Pascal WriteLn and Java was Println?
| aodonnell2536 wrote:
| Java has the beloved and hilariously verbose
| System.out.println
| mikewarot wrote:
| >Just like Pascal/Modula-2, except 30 years later :-D
|
| Now if they could fix the CaSe SeNSiTivity bug, get rid of
| macros, get some better strings, and maybe use @ to get an
| address, and ^ to point to things.... they'd be on to
| something.
|
| Especially getting rid of macros, I hate them.
|
| Most C code looks like line noise, maybe they could then use
| something like Begin/End to denote blocks instead of abusing
| comment markers {} ;-)
| CoastalCoder wrote:
| > Genuinely surprised and confused by "print" and "println"
|
| I was too. Looking at [0], I'm guessing it's meant to bring
| these benefits over old-school "printf":
|
| - Leverages the type system to avoid mismatches between the
| format string and the arguments.
|
| - Adds Python- / Rust-style support for position-based
| substitutions, e.g. "{1}".
|
| - Like Rust (I think; I'm still learning Rust) you can specify
| per-type formatters.
|
| But I'm still confused about:
|
| - How this relates to the standard library's iostream system.
| It seems pretty redundant.
|
| - Why "println" exists.
|
| [0] https://en.cppreference.com/w/cpp/io/print
| Karliss wrote:
| Most of those benefits apply to std::format which was already
| introduced in c++20. But formatting a string you will often
| want to output it somewhere. You could do `std::cerr <<
| std::format(....)`, but that just invites weird mixes of
| std::format and iostream based formatting. I look at
| print/println as partially just convenience function combing
| the existing std::format functionality with outputing to
| something. Not sure if standard permits it but print could
| also allow skipping some temporary heap allocated std::string
| which the std::format returns by directly writing to the
| output stream buffer.
|
| In C++20 if you wanted to print using std::format style
| formating (or it's variant) your options where:
|
| ```
|
| std::cout << std::format("{}{}", arg1, arg2);// not the best
| syntax but less bad than everything else, slightly
| inefficient due to temp string
|
| std::string tmp = std::format("{} {}", arg1, arg2);
| fwrite(stdout, tmp.c_str(), tmp.length()); // more ugly, and
| still uses temporary string
|
| std::format_to(std::ostream_iterator<char>(std::cout), "{}
| {}", arg1, arg2); // avoids temp string, but what kind of
| monstrosity is this
|
| ```
|
| But doesn't the std::format style formatting make the
| formatting part of ostream redundant -> it somewhat does. I
| guess that's why there are 3 types of print overloads:
|
| * accepting ostream as first argument
|
| * accepting c style "FILE*" as first output argument
|
| * no output stream argument, implicitly outputting to stdout
|
| One potential consideration to use ostream based versions
| instead of FILE* ones even though the formatting part is
| somewhat redundant, is RAII based resource management. If you
| want to use FILE* based versions, it means that you have to
| either remember manually close the File* handle which just
| like manual new/delete or malloc/free calls is something
| modern C++ tries to move away, or you have to create your own
| RAII wrappers around c style FILE* handles.
|
| An alternative would have been introducing new kind of output
| stream API which only concerns with outputing buffered stream
| of bytes and skips the formatting part of ostream, but that
| would have introduced different kind of redundancy -> having
| 3 kinds of API for opening and writing to file. Allowing to
| pass ostream to print, while discouraging use of << operator
| parts of it seems like a simpler solution.
|
| One more concern is how using the version of print which
| outputs to stdout without taking output File or ostream
| handle interacts with any buffering done by previous
| printf,and std::cout APIs and also synchronization with input
| streams. The same problem already existed before for
| interactions between printf and std::cout. For the most part
| if you don't mix them it doesn't matter too much, but if you
| care the standard defines how exactly they are synchronized.
| The new print probably either adds third case to this or is
| defined as being equivalent to one of the previous two.
|
| After looking reading docs a bit more, seems like
| std::basic_streambuf/std::file_buf did exist. The new print
| API, might have used that as abstraction around output
| streams without some of the ostream formatting baggage. I
| have only seen them mentioned as implementation details of
| ostream stuff, never seen anyone use them directly.
|
| There was also std::format_to in c++20 which avoid the
| temporary string std::format returns. I guess they could have
| extended that with additional overloads so that it can
| function more similar to std::print. But if they need to
| define new functions might as well call them something more
| familar to people coming from other languages like print,
| instead of having format_to(stdout, "formatstring", arg1,
| arg2);. Currently std::format_to outputs to output iterator.
|
| So to sumarize why print exists: - combine std::format
| introduced by C++20 with outputting somewhere with cleaner
| syntax compared to doing it manually. - sane
| looking hello world for beginner and people coming from other
| programming languages, this is probably also why println
| exists - (maybe) avoid some heap allocations
| that temporary string returned by std::format would cause.
| - avoid confusion of mixing two formatting approaches that
| `std::cout<<std::format()` implies - avoid C style
| manual resource management that would be required for doing
| `File\* f=fopen("");auto tmp=std::format();write(f,
| tmp.c_str())`
| dxuh wrote:
| iostreams are horrible to use, especially if you want to
| format your output and they blow up compilation to a mythical
| degree. People go to great lengths to avoid them and I agree
| with them.
|
| print/println are based on the fmt library
| (https://github.com/fmtlib/fmt), which had it's first release
| in 2015, so it's roughly as old as Rust afaik. It's mainly
| inspired by Python formatting.
|
| Having per-type formatters is just a logical conclusion of
| having type-safe formatting.
|
| iostreams are for all kinds of io (which includes
| stdin/stdout), while fmt is entirely dedicated to formatting
| strings. Those things are related, but not the same. cout and
| cerr and such will probably be entirely superseeded by
| print/println (I hope), but it doesn't make iostreams
| generally redundant.
|
| println adds a newline and you want to be able to choose, so
| there is print and println.
| dxuh wrote:
| What's surprising to you about this? I think they are generally
| regarded as very welcome additions.
| soulbadguy wrote:
| Really excited about the deducing this features. Fun template
| kung-fu'ing ahead.
| bhouston wrote:
| Does C++ yet have something similar to async/await?
|
| I have a medium sized JavaScript codebase that uses this
| (http://github.com/bhouston/behave-graph) and I could really use
| a port to C++ so that it can be integrated with the USD project,
| who has expressed interest.
|
| I couldn't find an equivalent to async/await in my searches so I
| am fearful that porting this across to C++ is non-trivial.
| __jem wrote:
| https://en.cppreference.com/w/cpp/language/coroutines
|
| But highly doubt you need anything like async/await for this
| kind of application. In fact, I'd go as far as to say
| async/await is almost never needed except for building
| networked services with truly massive performance demands.
| Kranar wrote:
| If you genuinely have massive performance demands, stay far
| away from coroutines. For whatever reason the approach C++
| took makes them incredibly memory hungry and inefficient.
| colatkinson wrote:
| std::expected and a monadic interface for std::optional are very
| welcome changes. I've ended up with funky utility types to
| accomplish much the same thing in a couple projects, so an
| official interface is definitely nifty.
|
| I remember reading that clang was finally shipping std as a
| module, albeit experimentally. So this ought to be an interesting
| couple of years for C++ -- though I suppose it remains to be seen
| to what degree the ecosystem will keep up with all these changes
| vs using headers/exceptions/the traditional ways of doing things.
| [deleted]
| dataflow wrote:
| Deducing this seems like a drastic change to the language, not a
| minor incremental one. People will be doing CRTP with it without
| realizing or fully appreciating the consequences now.
| quotemstr wrote:
| If they do, there's no harm done. The new this inference stuff
| is just a brevity enhancement, yes?
|
| You have to understand that it's a Sisyphean struggle to get
| people to use modern C++ features at all. You still see people
| passing around std::string instances by value and manually
| calling new and delete. They're ignorant of variants and think
| "final" is an advanced feature. I'm happy if they get as far as
| understanding even how to use CRTP.
|
| There's a vast gulf in skill between the expert C++
| practitioner who appreciates a blog post like the one linked
| and one who's been dragooned without training into writing some
| enterprise C++ thing. The latter relates to C++ as an
| inscrutable Lovecraftian god who might eat him slightly later
| if he makes the right cultist noises and does the right syntax
| dance.
| dataflow wrote:
| I wouldn't call it harmless. The feature might just be
| brevity enhancement for CRTP (and I think it's incredibly
| well-designed for that), but the advertisement/education
| around it usually just mentions constness-agnosticism and
| code deduplication as the use cases, which are precisely the
| _wrong_ cases for deducing this. CRTP was never the solution
| for those and that hasn 't changed with the syntax -- because
| both of these have effects on code semantics, not just
| syntax. But I will bet most people using it will use it for
| those purposes, rather than as a briefer syntax for when they
| would've otherwise used CRTP.
|
| It feels a lot like the push_back -> emplace_back thing,
| where the feature has important use cases, but plenty of
| people will use it merely because they're mistaught that it's
| the "modern way" to do things without being told it's a
| footgun that bypasses existing safety features in the
| language, and it becomes impossible to hold back the flood.
| And they get away with it the majority of the time, but every
| now and then they end up introducing subtle bugs that are
| painful to debug.
|
| But hey it's the shiny modern hammer, so _obviously_ you can
| 't just let it sit there. People can't just see a modern
| hammer and let you watch it sit there until you actually need
| it. You _have_ to use it (and people will tell you to use it
| during code reviews) or everyone will look down on you for
| being so old-fashioned and anti- "progress".
| germandiago wrote:
| It can also pass this by value, which is good for
| optimization. And it gets read of pointer-to-member
| functions, which is great.
| jeremyjh wrote:
| Probably thats also partly because they are being dumped into
| a large sprawling codebase already full of C++98 idioms. Even
| if you point them to the "newer sections" that are more
| modern, they will fall back to what they are working with all
| the time.
| germandiago wrote:
| There is yet another thing with deducing this: no more
| pointer-to-members:
|
| ``` class A { int f(this A & self, float b); }; ```
|
| Type of &A::f is int ( _)(A &, float), not int (A::_)(float).
|
| This is huge for template metaprogramming function deduction
| if you stick to it because that generated a lot of template
| instantiations to cover all cases.
| quotemstr wrote:
| But then you'll break if someone supplies a "real" member
| function. Is this even a big deal with std::invoke, which
| lets us treat regular functions and PMFs uniformly?
| kevstev wrote:
| When I was doing C++, one of my interview questions was an open
| ended one: "std::cout << "Hello world!" << endl;" What exactly
| is this doing, lets dive in to how it works.
|
| You touch on kind of a lot here, and its pretty esoteric to
| even devs with 3-5 years experience. functors, operator
| overloading, namespaces, passing by reference to make the <<
| chaining work, there is a lot you really have to know that is
| non-obvious. You can even jump into inheritance, templates and
| such if you really want to dive deep.
|
| I thought this was normal after doing C++ for ~11 years, but
| when I finally broke out of the Stockholm syndrome and started
| a job working in other languages, I find it absurd how complex,
| or maybe a better way to put it is, how much language specific
| trivia you need to learn before you can even begin to unravel
| how a basic hello world! program really works.
| imron wrote:
| That's a really great question.
|
| My favourite C++ question is asking what std::move does. It's
| amazing the knots people twist themselves into explaining how
| it supposedly works, when in reality it's just a type cast to
| an r-value so that the move constructor/assignment gets
| called instead of the copy one.
| CoastalCoder wrote:
| I'm glad for this article.
|
| C++ has been my main language for a very long time, but I've been
| a grumpy skeptic of C++ since around C++14 due to the language
| spec's total complexity. So I've mostly stuck with C++11.
|
| But now that C++ has modules, concepts, etc., I'm starting to
| wonder if C++23 _is_ worthwhile for new projects. I.e., the
| language-spec complexity is still there, but the new features
| might tip the balance.
|
| I'd been thinking to walk away from C++ in favor of Rust for new
| projects. But now I might give C++23 a chance to prove itself.
| jb1991 wrote:
| Most serious C++ developers will tell you that C++14 was
| basically a bug fix for some oversight in the C++11 spec. You
| should probably use it if you can if that's the standard you're
| happy with.
| constantcrying wrote:
| >I'd been thinking to walk away from C++ in favor of Rust for
| new projects. But now I might give C++23 a chance to prove
| itself.
|
| Modules were a great step forward for C++, but one of the
| features I enjoy the most about Rust is that adding a library
| _just works_. Especially for single person projects, which are
| on the smaller side, it feels absolutely great having to do
| near zero build management.
| [deleted]
| corysama wrote:
| C++ deserves its rep for complexity. But, it comes from a
| promise to avoid a version upgrade debacle in the style of
| Python 3. C++ promises that you will forever be able to
| interleave new-style code right into the middle of ancient,
| battle-tested old-style code.
|
| To do that, it can only add features and never take them away.
| Instead, it adds features that deprecate the practice of PITA
| patterns that were common, necessary and difficult.
|
| Like, SFINAE was necessary all over the place to make libraries
| "just work" the way users would expect. But, it is a PITA to
| write and and PITA to read. Now, constexpr if and auto return
| types can usually collapse all that scattered, implicit,
| templated pattern matching down to a few if statements. Adding
| those features technically made the standard more complicated.
| But, it made new code moving forward much simpler to
| understand.
|
| Similarly: Before variadic templates, parameter packs and fold
| expressions, you had the hell of recursive templates. Auto
| lambdas make a lot of 1-off templates blend right into the
| middle of regular code. Deduction guides set up library writers
| to set you up to write std::array
| names{"Alice", "Bob", "Charlie};
|
| instead of std::array<const char*, 2>
| names{"Alice", "Bob", "Charlie};
| munificent wrote:
| _> But, it comes from a promise to avoid a version upgrade
| debacle in the style of Python 3._
|
| There is a very wide middle ground between C++'s "Your
| horrific unsafe code from the 80s still compiles" and
| Python's "We changed the integer values common operations on
| strings return at runtime with absolutely no way to
| statically tell how to migrate the code".
|
| In Dart, we moved to a sound static type system in 2.0, moved
| to non-nullable types in 2.13 (sound and defaulting to non-
| nullable!), and removed support for the pre-null safety type
| system in 3.0. We brought almost the entire ecosystem with
| us.
|
| Granted, our userbase is much smaller than C++'s and the
| average age of a given Dart codebase is much younger.
|
| But you _can_ deprecate and remove old features without
| causing a decade of misery like Python did. You just need
| good language support for knowing which version of the
| language a given file is targeting, good static typing
| support, and good automated migration tooling. None of those
| is rocket science.
| plonk wrote:
| > But you can deprecate and remove old features without
| causing a decade of misery like Python did. You just need
| good language support for knowing which version of the
| language a given file is targeting, good static typing
| support, and good automated migration tooling. None of
| those is rocket science.
|
| That's way easier to do in a naturally statically-typed
| language though.
| munificent wrote:
| Yes, which C++ is.
| ajross wrote:
| Really the cognitive load of modern Rust is no less than
| C++$RECENT in my experience. Both require a ton of prima-facie
| concepts before you can productively read code from other
| developers. Of en vogue languages, Zig is the only one that
| seems to view keeping the "metaphor flood" under control as a
| design goal, we'll see how things evolve.
|
| But really, and I say this from the perspective of someone in
| the embedded world who still does most of his serious work in C
| and cares about code generation and linkage: I think the whole
| concept of these Extremely Heavy Systems Programming Languages
| is not long for this world. In modern systems, managed runtimes
| a-la Go/Swift/JVM/.NET and glue via crufty type-light
| environments like Python and Javascript are absolutely where
| the world has ended up.
|
| And I increasingly get the feeling that those of us left
| arguing over which monstrosity to use in our shrinking domain
| are... kinda becoming the joke.
| spoiler wrote:
| > Really the cognitive load of modern Rust is no less than
| C++$RECENT in my experience. Both require a ton of prima-
| facie concepts before you can productively read code from
| other developers.
|
| Eh. I don't know if I agree. I've worked on a few large C++
| codebases, and the cognitive load between Rust and C++ is
| incomparable.
|
| The ownership/borrowing stuff is complexity you deal with
| implicitly in other systems languages, here's it's just more
| explicit and semi-automated most of the time by the compiler.
|
| In C++ the terse thing is never the correct thing. If I'm
| using metaphors: a Rust sentence, in C++ usually has to be
| expressed through one or more paragraphs. The type system is
| so loose you have to do "mental" expansions half the time (or
| run the build and pray for no errors, or at the very least
| that the error is somewhat comprehensible[1]).
|
| There's some low-level stuff that can be ugly (for various
| definitions of ugly), but that's every language. The low
| level bits of async are a bit wired, but once the concepts
| "click" it becomes fairly intuitive.
|
| At least the ugly parts are cordoned behind library code for
| the most part, and rarely leak out.
|
| I guess it could just boil down to familiarity, but it took
| me much less time to familiarise myself with Rust than it
| took me to familiarise myself with C++. We're talking months
| vs years to consider myself comfortable/adept at it.
| Although, maybe just some C++ or general programming wisdom
| transferred over?
|
| [1]: This happens in Rust too, I must admit. But it's usually
| an odd situation that I've encountered once or twice with
| some very exotic types. In C++ it's the norm, and usually
| also reported with bizarre provenance
| tazjin wrote:
| C++ errors are so bizarre that most of the time I use the
| compilation result as a simple binary value. Some part of
| one's mental neural network learns how to locate many
| errors simply through a "it doesn't work" signal.
| [deleted]
| nunuvit wrote:
| I think the future of these languages is largely as a target
| for code generation and transformation. Their legacy tooling-
| unfriendly designs is what's slowing this transition.
| jamil7 wrote:
| I don't write embedded software, but I see Rust breaking into
| many domains outside of systems and winning developer mind
| share everywhere. Seemingly fulfilling its promise of being
| able to target low-level and higher-level software domains
| (something Chris Lattner wanted for Swift, but it hasn't yet
| materialised on the low-level side, we'll see).
| synergy20 wrote:
| I'm in doubt of this statement, Rust is here for 17 years,
| its market share is less than 1% still(per google).
|
| It does have a lot of developers saying good words for it
| whenever there is a chance in recent few years, but the
| reality is that, it's hard for most developers to learn,
| because of that it might remain to be a niche language for
| system programming.
|
| c++ is not standing still, I feel since c++20 it becomes a
| really interesting modern language, and I hope its memory
| safety concern will be addressed over time at a faster
| pace, in fact if you use rule-of-zero, RAII, smart pointers
| etc correctly your code can be very much memory-safe
| already.
| gonzus wrote:
| According to Wikipedia, Rust appeared for the first time
| on May 15, 2015; 8 years ago.
| CoreyFieldens wrote:
| That was the release date of Rust 1.0. Not the first
| appearance.
| the_duke wrote:
| Rust was a very different language pre 1.0 (GC, builtin
| green threading, etc). I think it's fair to start
| counting at 1.0.
| aseipp wrote:
| IMO the cognitive load of Rust is different in practice just
| from basic nuts and bolts things like having a ecosystem of
| libraries that culturally emphasizes things like portability
| and heavy testing, and a standard package manager and an
| existent module system (C++26 fingers crossed). I dislike
| Cargo but it's incredibly effective in the sense any Rust
| programmer can pick up another project and almost all of of
| the tools instantly work with no change. I mean, Rust is
| frankly most popular in the same space Go/Swift et cetera
| are, services and application layer programming, where those
| conveniences aren't taken for granted. I see it used way more
| for web services and desktop/application services/command
| line/middleware libraries than I do for, like, device drivers
| or embedded kernels. Those are limited enough in number
| anyway.
|
| Really, the ergonomics of both the language features and
| standard tooling meant it always meant it was going to appeal
| to people and go places C++ would not, even if they in theory
| are both "systems languages" with large surface areas, and
| they overlap at this extreme end
| (kernels/embedded/firmware/etc) of the spectrum that little
| else fits into.
| pjmlp wrote:
| Actually if you look back to 8 and 16 bit home computers,
| with C, C++, Object Pascal, BASIC, Modula-2, full of inline
| Assembly, in many cases that being the biggest code surface,
| it is quite similar.
|
| Nowadays C and C++ took the role of that inline Assembly,
| with higher level systems languages taking over the role of
| the former.
| ajross wrote:
| Sure. The point was more that Big System Engineering (in
| any particular domain, frankly including things like OS
| kernels), going forward, just won't be happening much in
| Rust or C++. And languages that large make for very poor
| "inline assembly", where C obviously does quite well and
| Zig seems to have a reasonable shot.
|
| I mean, literally yesterday I had to stop and write a
| "device driver" (an MMIO firmware loader for an on board
| microcontroller) in Python by mapping /dev/mem. That's the
| kind of interface and development metaphor we'll be seeing
| in the longer term: kernel driver and interface
| responsibilities will shrink (they are already) in favor of
| component design done in pedestrian languages like Go or
| whatever that mere mortals will understand. There's no
| market for a big C++ or Rust kernel framework.
| pjmlp wrote:
| I get the same feeling regarding many features on C#, F#, Java,
| Python, Typescript, Scala, Kotlin, Swift.
|
| Don't forget it isn't only the language, the various
| implementations, standard library, breaking changes, and major
| 3rd party libraries.
|
| So I embrace complexity, as the alternative is being stuck with
| something like Go.
| Koshkin wrote:
| It's either the complex language or the complex code.
| synergy20 wrote:
| Hopefully clang++ can catch up to the new standard faster as
| clangd the LSP is used in so many intellisense tools that depends
| on clang++ implementation. Even though c++23 will be compiling
| fine with newest g++, clangd will still complain all those new
| syntax probably for a few years ahead, at the moment quite a few
| c++20 features are still unsupported by clangd(which depends on
| clang++).
|
| or, gcc can have its own c/c++ LSP, which is not the case.
| enriquto wrote:
| #include <print> int main() {
| std::print("hello, world!\n"); return 0;
| }
|
| So, it has come to this. Much better than overloading bit-shift
| operators for input/output.
| fractalb wrote:
| Time to rewrite helloworld programs. IIRC, Java is also making
| it simpler to a helloworld program.
| JackSlateur wrote:
| So: int main() { printf("hello,
| world!\n"); return 0; }
|
| C knew it all along :)
| Sniffnoy wrote:
| I mean, the function for straight printing is puts; I don't
| know why people keep using the much more complicated printf
| in cases where no formatting is involved.
|
| Edit: OK, I guess puts includes a newline, so you'd need to
| use fputs if you don't want that (although this example
| includes one). Still, both of those are much less complicated
| than printf!
| izoow wrote:
| Consistency. Having intermixed puts and printfs throughout
| the code looks pretty bad. Also, every compiler replaces
| printf of a literal ending with \n with a puts anyway.
| qalmakka wrote:
| ... Except the fact that printf/scanf use variadics, and the
| only reason why it stopped being a constant source of crash
| is the fact that compilers started recognizing it and
| validating format strings/complaining when you pass a non-
| literal string as a format.
|
| <format> is instead 100% typesafe. If you pass the wrong
| stuff it won't compile, and as {fmt} shows you can even
| validate formats at compile time using just `constexpr` and
| no compiler support.
| jstimpfle wrote:
| As always, people making more fuss around it than
| necessary. Code calling printf() with a constant format
| string literal is this class of code that you have to run a
| single time to know it works. Many C++ programmers have
| always been using printf() in preference to e.g. std::cout
| because of ergonomics. And they were right.
|
| It's hard to take people seriously that try to talk it down
| for being a pragmatic solution that's been around for
| probably 30-40 years.
| catskul2 wrote:
| > is this class of code that you have to run a single
| time to know it works
|
| Not sure what you're trying to say here.
| [deleted]
| jstimpfle wrote:
| Take as an example _printf( "%d\n", foo->x);_. Assuming
| it compiled but assuming no further context, what could
| break here at run-time? _foo_ could be NULL. And the type
| of _foo- >x_ could be not an integer.
|
| Let's assume you run the code once and observe that it
| works. What can you conclude? 1) foo was not NULL at
| least one time. Unfortunately, we don't know about all
| the other times. 2) foo->x is indeed an integer and the
| printf() format is always going to be fine -- it matches
| the arguments correctly. It's a bit like a delayed type
| check.
|
| A lot of code is like that. Furthermore, a lot of that
| code -- if the structure is good -- will already have
| been tested after the program has been started up. Or it
| can be easily tested during development by running it
| just once.
|
| I'm not saying it's impossible to write a bad printf line
| and never test it, only to have it fail years later in
| production. It's absolutely possible and it has happened
| to me. Lessons learned.
|
| I'll even go as far as saying that it's easy to have
| errors slip on refactors and there aren't good tests in
| place. But people are writing untyped Python or
| Javascript programs, sometimes significant ones. Writing
| in those is like every line was a printf()!
|
| But many people will through great troubles to achieve an
| abstract goal of type safety, accepting pessimisations on
| other axes even when it is ultimately a bad tradeoff.
| People also like to bring up issues like this endlessly
| like it's the end of the world, when it's not nearly as
| big of an issue most of the time.
|
| Another similar example like that are void pointers as
| callback context. It is possible to get it wrong, it
| absolutely happens. But from a pragmatic and ergonomic
| standpoint I still prefer them to e.g. abstract classes
| in a lot of cases due to being a good tradeoff when
| taking all axes into account.
| mort96 wrote:
| I've definitely written bugs where the format specifier
| is wrong. Maybe you used %lu because a type happened to
| be unsigned long on your system, then you compile it for
| another system and it's subtly wrong because the type
| you're printing is typedef'd to unsigned long long there.
| Printing an uint32_t? I hope you're using the PRIu32
| macro, otherwise that's also a bug.
| repsilat wrote:
| Sometimes the print statement is in untested sanity-
| checking error-case branches that don't have test
| coverage ("json parsing failed" or whatever). It's pretty
| annoying when those things throw, and not too uncommon.
|
| Another case in C++ is if the value is templated. You
| don't always get test coverage for all the types, and a
| compile error is nice if the thing can't be handled.
|
| "Type coverage" is pretty useful. Not a huge deal here I
| agree, but nice nonetheless.
| tialaramex wrote:
| > Code calling printf() with a constant format string
| literal is this class of code that you have to run a
| single time to know it works void
| msg(char *msg, char *x, int k) {
| printf("%hhn%s%d\n", x, msg, k); }
|
| So, how about it? I mean, I have code where that works
| exactly as expected, so I can "know it works" according
| to you, but I _also_ have code where that blows up
| immediately, because it 's complete nonsense. Which
| according to you shouldn't happen, but there it is.
| Dylan16807 wrote:
| Printf is fine until you have to deal with the "f".
|
| Some compilers can keep format strings type-safe, but they
| are going above and beyond the standard to make it happen.
| tristan957 wrote:
| If you are using a compiler that doesn't support this,
| developer tooling like clangd and clang-tidy can help catch
| these issues too.
| Dylan16807 wrote:
| Today, yes. But it's relevant for a claim that C had it
| right "all along".
| kazinator wrote:
| * * *
| nuancebydefault wrote:
| Are there now in c++, after all these years, f-strings like
| python has, or at least something coming close? If not, I keep
| being at my disappointed state about c++.
| Longhanks wrote:
| Yes, already since C++20. But if you really would have wanted
| to change your mind, you could've found out easily.
| LordDragonfang wrote:
| >Already since C++20
|
| ...Or phrased another way, they _finally_ added them in
| _the latest spec_ (which the majority of C++ devs aren 't
| even using yet)
| [deleted]
| akhosravian wrote:
| C++23 was finalized nearly six months ago so it is not
| correct to call C++20 the "latest spec".
| LordDragonfang wrote:
| I should have said the "latest standard", not "spec", if
| we're being technical. But EVERY bit of official material
| is very clear about asserting that C++23 is still a
| preview/in-progress, not a standard. Saying otherwise is,
| strictly speaking, incorrect.
|
| https://isocpp.org/std/the-standard
|
| https://www.iso.org/standard/79358.html
|
| https://github.com/cplusplus/draft/blob/main/papers/n4951
| .md
|
| And quite frankly, what matters to devs is what tooling
| supports the specification without special configuration,
| and the answer is "basically none". Not a single compiler
| fully supports it.
|
| https://en.cppreference.com/w/cpp/compiler_support/23
|
| Also, side note, I'd be surprised if more than a third of
| C++ devs were even using _C++20_ , based on last year's
| numbers showing only ~25% adoption:
|
| https://www.jetbrains.com/lp/devecosystem-2022/cpp/
| psychphysic wrote:
| Most people don't have time to spend repeatedly checking
| for features in languages they don't use.
| Longhanks wrote:
| If people don't have time to keep up with a languages
| updates (which, in case of C++, is currently _once_ every
| three years), then they don't have time to complain about
| the lack of features, either. This one had the time to
| complain and just didn't want to bother typing "c++
| string formatting", which would have been fewer
| keystrokes than the comment complaining.
|
| On DuckDuckGo, the very first result for "c++ string
| formatting" is the exact thing this person was
| complaining about.
| psychphysic wrote:
| But then he'd have missed out on your conversational
| effervescence.
| nuxi wrote:
| OP asked about "f-strings like python has", which is
| different from plain std::format().
| nuancebydefault wrote:
| That about wanting to change mind... it touches a string!
| Somehow the let-downs must have been too much for me at a
| certain point in time. That being said, I'm curious to find
| out right now. Edit... no such thing found as string
| interpolation in cpp, at least not in my first 4 search
| hits. I'll crawl back.
| Conscat wrote:
| String interpolation is currently blocked by:
|
| - template-function parameters (NTTPs with a function
| parameter syntax rather than a template parameter syntax,
| tentatively spelled as `void foo(template int
| constant){}` )
|
| - Scalable reflection, in combination with expansion
| statements (likely in C++26, spelled `template for (auto&
| e : array) {}` ) which would allow you to write an
| arbitrary parser for the template string into real C++
| code. Reflection targets C++29.
|
| Syntax2 already supports string interpolation as a
| special compiler feature.
| coldtea wrote:
| std::string formatted_str = std::format( "My name is {}
| and my favorite number is {}", name, num);
| 0xffff2 wrote:
| That's really nothing like python strings, which for the
| same string would be formatted_str =
| f"My name is {name} and my favorite number is {num}"
|
| The function call I could forgive, but the the ability to
| embed arbitrary python expressions into the f-string
| directly is a huge readability win IMO.
| interroboink wrote:
| Meanwhile Perl's over here like "we had this in the 90s!"
| (:
|
| eg: perl -e '$x="AAA"; print
| "lowercase: ${\( lc($x) )}\n"'
| gpderetta wrote:
| Of course posix shells gad that even before. How far we
| can go? What's the first language that had something that
| resembles string interpolation?
| interroboink wrote:
| I'm sure someone here can say something about Lisp
| machines (:
| LordDragonfang wrote:
| Slightly off topic, but I recently learned that implementing
| the opposite of what you've asked for, _bitshift stdout in
| python_ , is only a few lines of code:
| class cppstream: def __lshift__(self, right):
| print(right, end='') return self cout
| = cppstream() endl = "\n" cout <<
| "This" << " is" << " cursed" << "!" << endl
| ziml77 wrote:
| I hate that... but actually less so than I hate Pandas'
| abuse of operator overloading on the indexing operator.
| enriquto wrote:
| You still have cstdio, which is perfectly cromulent.
|
| There's also a format-like thing using integers surrounded by
| curly braces, but it's horrific and I'm afraid to copy-paste
| it here.
| Conscat wrote:
| cstdio is not extensible, which in practice makes it
| useless for most C++ code.
| gpderetta wrote:
| And not type safe which make it even worse.
| thibran wrote:
| Where is the std:: part coming from / why is it not just
| print(...)?
| goalieca wrote:
| Hundreds of not thousands of people made their own print over
| the years. Namespacing is important.
| aodonnell2536 wrote:
| The function is part of the std namespace, just like
| std::cout.
| tialaramex wrote:
| C++ has namespacing which makes sense because this language
| has an enormous amount of available 3rd party libraries and
| without name spacing you can't help stepping on each others
| toes.
|
| There are two ways you might want to have this work anyway
| despite namespacing. One option would be that you just import
| the namespace and get all the stuff in it, this is popular in
| Java for example, however in C++ this is a bit fraught
| because while you can import a namespace, you actually get
| everything from that namespace and all containing namespaces.
|
| Because the C++ standard library defines a huge variety of
| symbols, if you do this you get almost all of those symbols.
| Most words you might think of are in fact standard library
| symbols in C++, std::end, std::move, std::array, and so on.
| So if you imported all these symbols into your namespace it's
| easy to accidentally trip yourself, thus it's usual not to do
| so at all.
|
| Another option would be to have some magic that introduces
| certain commonly used features, Rust's preludes do this, both
| generally (having namespaces for the explicit purpose of
| exporting names you'd often want together) and specifically
| ("the" standard library prelude for your Edition is
| automatically injected into all your Rust software by
| default). C++ could in principle do something like this but
| it does not.
| WalterBright wrote:
| D avoids the stepping on each other problem by using
| modules.
| dkersten wrote:
| The std namespace is from the <print> standard header. It's
| not just print because while you might want it in the global
| namespace, other people do not. For example, my code isn't
| cli and doesn't need to print to the cli, but perhaps I want
| to print to a printer or something else and have my own print
| function.
|
| Leave un-namespaced identifiers to those that are declared in
| the current file and namespace everything else. If you really
| want, you're free to add "using namespace std" or otherwise
| alias the namespace, but keeping standard library functions
| out of the global namespace as a default is a good thing! (In
| any language, not just C++)
| JohnFen wrote:
| > If you really want, you're free to add "using namespace
| std"
|
| You're free to, but I discourage the habit. It's more
| verbose to add the namespace:: prefix to symbols, but it
| sure does make it easier on the devs that have to work with
| the code later.
| turndown wrote:
| Is the include correct? Wouldn't it just be import std; ?
| kstrauser wrote:
| What's the difference between include and import now?
| gpderetta wrote:
| Modules.
| lostmsu wrote:
| Are they in C++23?
| gpderetta wrote:
| They are in C++20. But only MSVC has an usable
| implementation yet.
| Conscat wrote:
| #include is a preprocessor directive that substitutes the
| text of a file in place. import declares that this
| translation unit should link to a specified module unit.
| Usually there would only be a single translation unit for
| the entire program in the latter case, which obsoletes
| IPO/LTO (except when you have statically linked libraries),
| and means internal linkage functions (everything that is
| template, inline, or constexpr) do not have to be
| redundantly recompiled. That also means there would be no
| distinction between inline variables and static variables.
| This obsoletes unity builds and precompiled headers.
| KMag wrote:
| The include is correct.
|
| First, includes either need to be wrapped in angle brackets
| (for files included from the include path passed to the
| compiler) or quotes (for paths relative to the current file).
|
| Second, the whole standard library would be huge to pull in,
| so it is split into many headers, even for symbols in the top
| level of the std namespace.
| enriquto wrote:
| Apparently, the header print adds symbols to the std
| namespace, just like other standard headers:
|
| https://en.cppreference.com/w/cpp/header/print
| xxpor wrote:
| _puts on the most annoying code reviewer you 've ever met hat_
|
| acktually, you should use println.
| cjaybo wrote:
| Suggesting a function that doesn't exist in C++ definitely
| qualifies as annoying :D
| xxpor wrote:
| It exists in 23:
| https://en.cppreference.com/w/cpp/header/print
| Google234 wrote:
| Don't need the return 0?
| mhh__ wrote:
| Admittedly I grew up after their introduction but I've always
| viewed the whining about overloading the shift operators quite
| pointless.
|
| Of all the things C++ annoys me with, I don't care that much
| about syntax.
| amalcon wrote:
| I actually like operator overloading, but overloading the
| shift operators for I/O was still a mistake IMO. It's a
| mistake even if you ignore that it's a theoretical misuse
| (I/O and binary shifting have nothing to do with each other
| semantically). The operator precedence of the binary shift
| operators is just wrong for I/O.
| tomcam wrote:
| My guess is you never had to parachute into a project using
| operator overloading in strange, inconsistent, and
| undocumented ways with no original maintainers to show you
| the ropes
| mhh__ wrote:
| After all it's impossible to do this with regular code
| bippingchip wrote:
| If you just need a nice print: fmtlib is a really nice c++23
| style implementation without needing c++23 compiler support.
| Highly recommend it. It's simple. It's fast.
| kazinator wrote:
| * * *
| coldpie wrote:
| Operator overloading is an anti-feature and every language with
| it, would be better without it. Fight me.
| sgt wrote:
| Cage fight, or arguing about the feature?
| PartiallyTyped wrote:
| Exhibit a: Haskell.
| skeletal88 wrote:
| I wish that Java had it, so that adding and multiplying
| BigDecimals would not look as ugly as it does
| zeedude wrote:
| Not every day you get your wish.
|
| https://github.com/manifold-
| systems/manifold/tree/master/man...
| constantcrying wrote:
| It is a very natural feature. Especially when you are writing
| mathematical code e.g. implementing different types of
| numbers, e.g. automatic differentiation, interval arithmeic,
| big ints, etc.
|
| Overloading gives user defined types the expressiveness of
| internal types. Like _all_ features, of they are used badly
| (e.g when + is overloaded to an operation which can hardly be
| interpreted as addition) it makes things worse. But you can
| write bad code in any language, using any methodology.
| CobrastanJorji wrote:
| It is a very natural feature, but it makes discovering what
| you can and can't do with a library really hard. Learning
| what is and isn't legal with math libraries that use a lot
| of them can be really tricky. For example, numpy code is
| really easy to read, which is fantastic, but figuring out
| how you're intended to do things from the documentation
| alone is quite difficult.
| constantcrying wrote:
| In my experience numpy has also been on of the worst
| numerics libraries to deal with. The main reason is that
| Python seems designed to be hostile to numerics. Loose
| typing, assumptive conversions, specific numeric types
| hard to access, tedious array notations, etc. all are bad
| preconditions for a language which sadly seems to have
| become the prototyping standard in that area.
|
| The moment you have a language actually designed for
| numerics all these things vanish. One of Julias core
| design aspects is multiple dispatch, including operator
| overloading and it works extremely well there.
|
| I also don't see the point for discoverability at all.
| The documentation will list the overloads and the non-
| overloaded calls are exactly as discoverable as the
| others.
| spacechild1 wrote:
| Sigh. Do you really think that
| "(vec1.mul(vec2)).plus(vec3.mul(vec4))" is better than "vec1
| * vec2 + vec3 * vec4"? Thanks, but no thanks.
| Etherlord87 wrote:
| What if you could type the asterisk to multiply vectors,
| but then your editor of choice would replace it with the
| symbol that actually means vector multiplication?
|
| Perhaps that idea falls apart once you realize you would
| need hundreds of symbols for just addition...
|
| But what if those symbols were (automatically) imported and
| named at the beginning?
|
| Perhaps it would be annoyingly inconsistent how in various
| files different symbols are used for the same operation...
| JohnFen wrote:
| > then your editor of choice would replace it
|
| I realize this is a minority opinion, but I don't want my
| editor to replace anything unless I've deliberately told
| it to.
| gpderetta wrote:
| It could annotate the view without changing the source.
|
| For example my editor annotates auto with inferred types
| and function parameters with parameter names.
| dekhn wrote:
| In a certain pedantic, functional way, yes. For fast visual
| parsing, no.
| duped wrote:
| No but I think sum({ mul(vec1, vec2),
| mul(vec3, vec4) })
|
| definitely is.
| Q6T46nT668w6i3m wrote:
| Yes (I'm a numerical analysis researcher and wrote a
| handful of ubiquitous mathematical packages)! The
| implementation of even primitive types can vary
| considerably! It's way too much to hide. Nevertheless, I
| understand I'm biased. :)
| coldpie wrote:
| Try this on: plus( mul(vec1,
| vec2), mul(vec3, vec4))
|
| Yeah it's a tiny bit clumsier, and prefix notation takes
| some getting used to. But on the plus side we avoid all the
| too-clever travesties programmers have inflicted on us with
| bad operator overloading decisions! On the whole I think
| it's easily worth the trade.
| spacechild1 wrote:
| Again, no thanks. I want mathematical notation and I
| simply won't use any language without operator
| overloading. Free functions for common mathematical
| operations are an abomination.
| throwaway894345 wrote:
| Then you should probably use a language that lets you
| write DSLs for any given domain, rather than abusing
| operator overloading which just happens to work for a few
| subdomains of mathematics (e.g., you can't use
| mathematical conventions for dot product multiplication
| in C++). Anyway, I've never seen any bugs because someone
| misunderstood what a `mul()` function does, but I've
| definitely seen bugs because they didn't know that an
| operator was overloaded (spooky action at a distance
| vibes).
| eesmith wrote:
| Dealing with money is important, even if it's only a
| small part of mathematics. I'll focus on that.
|
| Python's 'decimal' module uses overloaded operators so
| you can do things like: from decimal
| import Decimal as D tax_rate = D('0.0765')
| subtotal = 0 for item in purchase:
| subtotal += item.price * item.count # assume price is a
| Decimal taxes = (subtotal *
| tax_rate).quantize(D('0.00')) total = subtotal +
| taxes
|
| Plus, there's support for different rounding modes and
| precision. In Python's case, something like "a / b" will
| look to a thread-specific context which specifies the
| appropriate settings: >>> import decimal
| >>> from decimal import localcontext, Decimal as D
| >>> D(1) / D(8) Decimal('0.125') >>> with
| localcontext(prec=2): ... D(1) / D(8) ...
| Decimal('0.12') >>> with localcontext(prec=2,
| rounding=decimal.ROUND_CEILING): ... D(1) / D(8)
| ... Decimal('0.13')
|
| Laws can specify which settings to use, for examples,
| https://www.law.cornell.edu/cfr/text/40/1065.20 includes
| "Use the following rounding convention, which is
| consistent with ASTM E29 and NIST SP 811",
| (1) If the first (left-most) digit to be removed is less
| than five, remove all the appropriate digits without
| changing the digits that remain. For example, 3.141593
| rounded to the second decimal place is 3.14.
| (2) If the first digit to be removed is greater than
| five, remove all the appropriate digits and increase the
| lowest-value remaining digit by one. For example,
| 3.141593 rounded to the fourth decimal place is 3.1416.
| ... (I've left out some lines)
|
| and from https://www.law.cornell.edu/cfr/text/7/1005.83 :
| (3) Divide the result in paragraph (a)(2) of this section
| by 5.5, and round down to three decimal places to
| compute the fuel cost adjustment factor; (4)
| Add the result in paragraph (a)(3) of this section to
| $1.91; (5) Divide the result in paragraph
| (a)(4) of this section by 480; (6) Round the
| result in paragraph (a)(5) of this section down to five
| decimal places to compute the mileage rate.
|
| There's probably laws which require multiple and
| different rounding modes in the calculation.
|
| This means simply doing all of the calculations in scaled
| bigints or as fractions won't really work.
|
| Now of course, you could indeed handle all of this with
| prefix functions and with explicit context in the
| function call, but it's going to be more verbose, and
| obscure the calculation you want to do. I mean, it's not
| _seriously_ worse. Compare: with
| localcontext(prec=3, rounding=decimal.ROUND_DOWN):
| line3 = line2 / D("5.5") line4 = line3 + D("1.91")
| line5 = line4 / 480 line6 =
| line5.quantize(D('.00001'), rounding=decimal.ROUND_DOWN)
|
| vs. some function-based API with overloaded parameter
| types: line3 = decimal_div(line2,
| D("5.5"), prec=3, rounding=decimal.ROUND_DOWN)
| line4 = decimal_add(line3, D("1.91")) line5 =
| decimal_div(line4, 480) line6 =
| decimal_quantize(line5, D('.00001'),
| rounding=decimal.ROUND_DOWN)
|
| But it is worse. I also originally made a typo in the
| function-based API for line5 where I used "decimal_add"
| instead of "decimal_div" - the symbols "/" and "+" stand
| out more, and are less likely to be copy&pasted/auto-
| completed incorrectly.
|
| If overloaded parameters - "spooky action at a distance
| vibes" - also aren't allowed, then this becomes more
| rather more complicated.
| pjmlp wrote:
| Assuming mul does actually a multiplication.
| spacechild1 wrote:
| Actually, I'm quite happy what C++ has to offer :)
|
| Yes, the * operator can be ambiguous in the context of
| classic vector math (although that is just a matter of
| documentation), but not so much with SIMD vectors, audio
| vectors, etc.
|
| Again:
|
| a) vec4 = (vec1 - vec2) * 0.5 + vec3 * 0.3;
|
| or
|
| b) vec4 = plus(mul(minus(vec1, vec2), 0.5), mul(vec3,
| 0.3));
|
| Which one is more readable? That's pretty much the
| perfect use case for operator overloading.
| spacechild1 wrote:
| Regarding the * operator, I think glm got it right: * is
| element-wise multiplication, making it consistent with
| the +,-,/ operators; dot-product and cross-product are
| done with dedicated free functions (glm::dot and
| glm::cross).
| fpoling wrote:
| One never writes such expression in a serious code. Even
| with move semantic and lazy evaluation proxies it is hard
| to avoid unnecessary copies. Explicit temporaries make
| code mode readable and performant:
|
| auto t = minus(vec1, vec2); mul_by(t, 0.5/0.3); add(t,
| vec3); mul_by(t, 0.3); v4 = std::move(t);
| MadcapJake wrote:
| If editors were to implement it, you could navigate to
| the corresponding overload implementation or even provide
| some hint text. Just like they do for other functions.
| dekhn wrote:
| plus(x, y) IS mathematical notation. It's Polish Notation
| without symbol shortcuts. Mathematica has used this for
| decades.
| danzk wrote:
| It's Polish Notation actually.
| dekhn wrote:
| Thanks, corrected in place.
| moshegramovsky wrote:
| [dead]
| bigbillheck wrote:
| Function overloading's not that much better.
| slavik81 wrote:
| Why have operators at all? If that notation is good
| enough, then you might as well use it for the built-in
| types too. We're halfway to designing a Lisp!
| ThemalSpan wrote:
| Sorry but please don't take Eigen
| (https://eigen.tuxfamily.org) away from me. Can't speak
| for others, but the scientific code I work on would
| become unreadable like that.
| sva_ wrote:
| The site seems to be down
|
| https://downforeveryoneorjustme.com/eigen.tuxfamily.org?p
| rot...
| pjmlp wrote:
| How can you be sure that plus() and mul() actually do
| what they say?
| coldpie wrote:
| They're functions. Whatever they do, my code will go
| execute some code from whatever library implements them,
| which is what a function does. I just want to be able to
| rely on [] being an array subscript when I read some
| unfamiliar code. Is that too much to ask?
| pjmlp wrote:
| Just like the function that happens to be named [].
| Etherlord87 wrote:
| I think in this context what's important is if these
| functions are overloaded or not...
| pjmlp wrote:
| Which in any case also means the only way to be sure is
| to track down the implementation, the actual character
| set hardly makes a difference.
| enriquto wrote:
| I agree with you. Function overloading is just as bad as
| operator overloading.
| throwaway894345 wrote:
| You read the code. And unlike operator overloading, you
| know at a glance exactly which implementation to look at.
| There is no spooky action at a distance.
| pjmlp wrote:
| Exactly the same thing with functions that happen to use
| funny characters.
| joshuamorton wrote:
| How, the functions might support dynamic dispatch or be
| overloaded based on types.
|
| There's nothing that prevents me from implementing all of
| plus(Int, Int) plus(Int, Vec) plus(Int,
| Mat) plus(Vec, Vec) plus(Vec, Mat)
| plus(Mat, Mat)
|
| and to know which `plus` is being dispatched, you need to
| know the types of both arguments, exactly the same as if
| `plus` is named `__add__` in python or `operator+` in
| C++.
| dleslie wrote:
| Even better: (plus (mul vec1
| vec2) (mul vec3 vec4))
|
| Lisp is love.
| Koshkin wrote:
| _Love is cruel; you could fall for a goat_ (a Russian
| proverb)
| fsloth wrote:
| That't literally the only place where operator overloading
| makes any sense.
|
| In other places they monkey patch c++ defincies as a
| language.
|
| And they are confusing and error prone.
|
| Nobody is pretending we will get rid of any c++ syntax
| ever. So the discussion is about a hypothetical language
| syntax that fits C++ slot.
|
| In that world C++ would have N x M matrices as native value
| types in the language (as fortran does) and those operators
| would be defined in the language spec for matrix types just
| as they are defined for standard number types at the
| moment.
|
| And it would not have any operator overloading.
| doodpants wrote:
| > That't literally the only place where operator
| overloading makes any sense.
|
| That may be true for C++ (I'll take your word for it),
| but not for all programming languages in general. For
| example, in C# it's fairly common to overload == and !=
| to implement value equality for reference types
| (classes).
|
| Of course, you should really only do this for immutable
| classes that are mostly just records of plain old data.
| And C# 9 introduced record classes, which is a more
| convenient way of defining such classes. But record
| classes still overload these operators themselves, so you
| don't have to do it manually.
| throwaway894345 wrote:
| Honestly that sort of thing always confused me when I
| worked in Java, C#, etc. I could never tell at a glance
| whether the operator was doing an identity comparison or
| a value comparison, and I definitely contributed a few
| bugs from this misunderstanding. In Go which lacks
| operator overloading, we either ` _ptr1 ==_ ptr2` or we
| do some `ptr1.Equals(ptr2)` for value comparisons and
| `ptr1 == ptr2` for pointer comparisons--in either case,
| there is no ambiguity and IME fewer bugs.
| gpderetta wrote:
| That's more of a problem with languages that confuse
| value types and references.
| wizofaus wrote:
| Java's the same regarding == and .equals( ) and when it's
| Java code written by devs who also work in other
| languages, it definitely still results in bugs, sometimes
| that go undiscovered for remarkably long times
| (particularly if == happens to return the right result in
| most cases). Meaning/needing to compare references for
| string (and similar) types is exceedingly uncommon, yet
| uses the more "natural" syntax for testing equality. FWIW
| I can't remember working with a codebase where unexpected
| behavior due to operator overloading was a serious
| problem.
| noduerme wrote:
| What about the assignment operator?
| crickey wrote:
| Making such assumptions about what is correct or proper
| use is why c++ is so successful, it doesnt make
| assumptions it leaves it up to the project / community
| using it. Go ahead make a language that dictates alot and
| makes srict assumptions it will be depricated or forced
| to open up before the end of the decade. Notr this is why
| i think python and lisp is so populare meta programing is
| very powerful and expressive.
| suby wrote:
| That's a good observation about C++ not making
| assumptions, it strikes me as true. C++ apparently
| doesn't even make assumptions about what the C++ filename
| extension is. .h, .hh, .hpp, .hxx, .C, .cc, .cpp, .cxx,
| .ixx, cppm
| [deleted]
| eesmith wrote:
| > That't literally the only place where operator
| overloading makes any sense.
|
| So, no overloading for decimal types? Nor fraction types?
|
| I may be the oddball here, but I've used operator
| overloading on those types more than I have for vectors
| and arrays.
| JohnFen wrote:
| Sure, there is a place for operator overloading.
|
| That said, in my experience over the decades, operator
| overloading has been one of the primary causes of bugs that
| are very hard to pin down, so I have come to hate it. It
| hides far too much.
|
| The cost/benefit ratio of operator overloading is generally
| unfavorable in practice, in my experience. Which is not to
| say it shouldn't be used when it actually clarifies things!
| But those situations tend to be fairly niche.
|
| Interestingly, where I work right now, using operator
| overloading is specifically prohibited. So I'm confident
| that my dislike of the practice is not just a personal
| quirk.
| bhouston wrote:
| As someone who has written math libraries over and over
| again for the last 25 years (I wish I was joking, but it
| turns out it is something I'm good at [1]), I find that
| operator overloading works only for the simple cases but
| that for performance and clarify, function names work best.
|
| Function names let you clarify that it is an outside
| product or inside product (e.g. there are often different
| types of adds, multiplies, divides), and I can not stand
| when someone maps cross product onto ^ (because you can
| both exponent and cross product some vectors, like
| quaternions, so why use exponent operator for cross?) or
| dot product onto something else that doesn't make any
| sense. Also operator overloading often doesn't make clear
| memory management, rather it relies on making new objects
| constantly, whereas with explicit functions, you can pass
| in an additional parameter that will take the result.
| Lastly, explicit functions allow you to pass additional
| information like how to handle various conditions, like
| non-invertible, divide by zeros, etc.
|
| I find word-based functions more verbose but significant
| less error prone and also they are more performant (because
| of the full control over new object creation.) Operator
| overloading is only good for very simple code and even then
| people always push it too far so that I cannot understand
| it.
|
| [1] 1997: https://github.com/bhouston/BezierCurveDemo1997/b
| lob/master/..., 2001: C# math library: https://github.com/b
| houston/ExoEngine3D/tree/master/Librarie..., 2013: a bunch
| of the core Three.js math library, 2023: https://github.com
| /bhouston/threeify/tree/master/packages/ma...
| spacechild1 wrote:
| > rather it relies on making new objects constantly,
| whereas with explicit functions, you can pass in an
| additional parameter that will take the result.
|
| We have move semantics since C++11.
| [deleted]
| gpderetta wrote:
| And expression templates even before that.
| kristiandupont wrote:
| Vectors and perhaps matrices are about the only valid use
| case I have ever come across, so I agree with GP that it's
| not worth it. And that's speaking as someone who once
| implemented a path class with a subtraction operator that
| would return the relative path between two absolute ones. I
| thought I was very clever. I feel sorry for the developers
| who had to figure out what that was about after I left..
| coldpie wrote:
| Ah, matrices! Does * mean dot product, cross product, or
| the usually less useful matrix multiplication? Ooh, or
| maybe you should you use . for dot! How clever that would
| be!
|
| > And that's speaking as someone who once implemented a
| path class with a subtraction operator that would return
| the relative path between two absolute ones. I thought I
| was very clever.
|
| Haha! It's ok. The temptation to be clever with operators
| is too strong, few can resist before getting burned (or
| more usually, burning others!) at least once.
| owalt wrote:
| > Ah, matrices! Does * mean dot product, cross product,
| or the usually less useful matrix multiplication? Ooh, or
| maybe you should you use . for dot! How clever that would
| be!
|
| Why the snark? The fact that you're free to make a bad
| choice does not imply that having a free choice must be
| bad. Obviously neither dot nor cross product should be *.
| It should be the Hadamard product or matrix
| multiplication. You can choose one convention for your
| code and be perfectly happy for it.
|
| As a follow-up question: How do you feel about languages
| like Fortran and Matlab then? Is it actually a good thing
| that mathematics convenience features are relegated to a
| few math-oriented languages and kept away from all the
| others? (Or are the linear algebra types in these
| languages offensive as well?)
| kps wrote:
| Maybe C++26 will let us write operator*() and
| operatorx().
| woooooo wrote:
| The benefits from operator overloading are "I can show
| this to a mathematician and it looks like what they're
| used to". The downsides lurk in the corners of whether
| it's actually doing what you think.
|
| I'll pass.
| Aardwolf wrote:
| I'll byte: complex numbers and matrix support is bad in
| languages without operator overloading. Why should only the
| primitive types of the language be privileged to proper math
| notation?
| pphysch wrote:
| Operator overloading is critical for building ergonomic
| frameworks.
|
| The modern web is built on overloading the . operator (e.g.
| ORMs like Rails and Django). We will never see a Tier-1 ORM
| in Golang simply because it lacks it.
|
| Gamedev, AI also benefits heavily from it.
| KRAKRISMOTT wrote:
| > _Operator overloading is critical for building ergonomic
| frameworks. The modern web is built on overloading the .
| operator (e.g. ORMs like Rails and Django). We will never
| see a Tier-1 ORM in Golang simply because it lacks it._
|
| Not true. It can be replaced with codegen.
|
| See Ent (spun out of Facebook I believe)
|
| https://entgo.io/docs/getting-started
| tester756 wrote:
| C#?
| zadjii wrote:
| I personally love glm[1] and the way you can just
| vec3 foo{1, 2, 3}; vec3 bar{2, 0, -2}; auto
| baz = foo + bar; // {3, 2, 1}
|
| [1]:
| https://github.com/g-truc/glmhttps://github.com/g-truc/glm
| tomcam wrote:
| Corrected link:
|
| https://github.com/g-truc/glm
| mhh__ wrote:
| What's wrong with being able to add my Complex or Dual
| numbers together?
| coldpie wrote:
| Whatever you save in avoiding having to write "sum = add(l,
| r)" is not worth allowing programmers to bitshift file
| handles[1], divide file path objects[2], or subscript
| objects to launch a process[3].
|
| [1] https://en.cppreference.com/w/cpp/io/cout
|
| [2] https://docs.python.org/3/library/pathlib.html
|
| [3] https://pypi.org/project/plumbum/
| projektfu wrote:
| [2] pathlib
|
| I quite like that one but I'd be willing to give it up
| for the hobgoblin of my little mind.
| Georgelemental wrote:
| Just because some library writers abuse language
| features, that doesn't mean everyone else who uses those
| features correctly should be punished.
| mhh__ wrote:
| C++ "bitshifts" (yes, but it's just a symbol in this
| context) make no real difference to me. It was type safe
| when it wasn't easy to be back in the day, everyone knows
| exactly how they work - it's never shot me in the foot
| (whereas the real meat of the design has, independently
| of how the composition is expressed syntactically).
|
| The others are more sus but you can make a bad API out of
| anything.
|
| Rewriting (say) a load of calculations as a tree of sum
| pow exp etc. is just a huge burden - a codebase I work on
| has a formula that takes up about 6 lines for example,
| outputted by mathematica: total pain to to translate to
| function calls.
| coldpie wrote:
| > The others are more sus but you can make a bad API out
| of anything.
|
| Yahhhh but there's something about operator overloading
| that is like catnip to clever programmers. Ah ha, file
| paths have slashes, and the divide operator is a slash!
| Clearly I should use the divide operator to append paths
| segments together! I'm so clever! Ah ha, << looks kinda
| like an arrow I guess, so we can use it to, uh, pass data
| between objects, I guess. I'm so clever...?
|
| It's irresistible. We have abused it and we must give it
| up for the good of all.
| smabie wrote:
| You want different addition functions for floats vs ints vs
| unsigned ints?
| sva_ wrote:
| Counter example: Any Tensor library ever
| enriquto wrote:
| You could argue that there is just one type (tensor) with
| some invalid operations between its values (e.g., when
| dimensions mismatch). Just like integer division by zero.
| Hemospectrum wrote:
| How far are you prepared to take this stance, exactly? C has
| operators that are generic over both integral and floating
| point types. Was that a mistake? Did OCaml do it better?
|
| For my part, I've been persuaded that generic operators like
| that are a net win for math-heavy code, especially vector and
| matrix math. Sure, C++ goes too far, but there are middle
| grounds that don't.
| fsloth wrote:
| Having operators defined for value types within the
| langauge spec is different thing than defining operator
| overloading for arbitrary struct and class types.
|
| For numeric value types mathematical operators are the only
| sane option.
|
| For arbitrary classes - not so much.
|
| A sane language in the slot of C++ in the language
| ecosystem would not have operator overloading. It would
| have matrix types defined in the language spec with
| mathematical operators operating on them.
| cogman10 wrote:
| > It would have matrix types defined in the language spec
| with mathematical operators operating on them.
|
| This is unfortunately impossible (IMO). The problem is
| matrixes have multiple operations that don't translate
| nicely like complex numbers do. If you want to be
| consistent, you have to pick and choose What A * B means,
| under which contexts, and when is that illegal (or what
| should happen on an error).
|
| For complex numbers, there's only one definition of A * B
| that matters and no failure cases.
|
| I fear there's not clean way to do matrix operations that
| won't make some community really irritated for choosing
| "wrong". (Physics, engineering, science, etc.)
| repsilat wrote:
| One part of the philosophy of the language maintainers is
| that they're somewhat humble about their designs in the
| standard library, and very much against breaking changes.
|
| Some folks prefer absl's flat_hash_map over
| std::unordered_map for a hash table, and it's not great
| that you need to choose or risk having both in a
| codebase, but it _is_ nice that you can have your
| preferred hash table and use operator[] whichever you
| decide.
|
| Python also has operator overloading, and people seem to
| like that numpy can exist using it. And container types.
| Weirdly doesn't cause much consternation compared to C++
| (maybe because the criticisms of the latter come from C
| programmers?)
|
| I've occasionally missed overloading in JS/TS though.
| AndrewStephens wrote:
| > Fight me.
|
| OK.
|
| Operator overloading is a useful feature that saves a bunch
| of time and makes code way more readable.
|
| You can quibble whether operator<<() is a good idea on
| streams and perhaps C++ takes the concept too far with
| operator,() but the basic idea makes a lot of sense.
| string("hello ") + string("world"); complexNumber2 *
| complexNumber2; for (int i : std::views::iota(0, 6)
| | std::views::filter(even) |
| std::views::transform(square))
| someSmartPtr->methodOnWrapperClass();
| coldpie wrote:
| All the things you wrote could be about as easily written &
| much more easily read without operator overloading.
| Operator overloading only allows programmers to feel
| "smart" for doing a "clever" thing, to the detriment of
| future readers. string("hello
| ").append("world");
| complexNumber2.mult(complexNumber2); // wtf is
| even going on with this one in your example? have these
| people never heard of method chaining? for(int i :
| std::views::iota(0,6).filter(even).transform(square))
| (*smartPtr)->methodOnWrapperClass();
|
| That's all about the same verbosity, it's much more clear
| to the reader even if they're unfamiliar with your
| codebase, and dropping operator overloading eliminates the
| "clever" option to do stupid crap like divide file path
| objects together.
| AndrewStephens wrote:
| Would you advocate getting rid of operators altogether?
| 3.times(2).plus(7)
|
| Some things just lend themselves to being expressed in
| terms of simple operators.
| (*smartPtr)->methodOnWrapperClass();
|
| That is still using the overloaded
| SmartPtr<>::operator*() method.
|
| I understand the viewpoint that operator overload is
| syntactic sugar for things that can easily be done
| another way, I just disagree that costs outweigh the
| benefits.
| coldpie wrote:
| > Would you advocate getting rid of operators altogether?
|
| Of course not. It makes sense for built-in types, as
| everyone reading the code can be assumed to know them.
|
| > That is still using the overloaded
| SmartPtr<>::operator*() method.
|
| Good catch ;)
|
| > I just disagree that costs outweigh the benefits.
|
| Yah, I think that's the disagreement. My feeling is
| there's a teeny, tiny handful of appropriate places for
| it (almost entirely math) and it opens up a pandora's box
| of terrible decisions that programmers clearly find
| irresistible.
| gpderetta wrote:
| C++ doesn't have extension methods, so you wouldn't be
| able to add custom algos to the set of chainable algos.
|
| Overloading operator| is a crude way to get the
| equivalent of extension methods without having to change
| the language.
| fsloth wrote:
| The majority of time in professional codebases is not spent
| on typing but reading and understanding code.
|
| "saves a bunch of time and makes code way more readable"
|
| Not when everybody defines their own operators.
|
| Note - we are discussing operator overloading, not
| operators as features in syntax. Operators at the syntax
| level make life a lot easier. But then _everybody uses the
| exactly same operator semantics_ , not some weird per-
| project abstraction.
|
| The lines of code you wrote as an example are not saving
| anyones time, except when writing it if you are a slow
| typist and lack a proper IDE support for C++. If typing
| speed is an issue, get a better IDE, don't write terser
| code.
| joshuamorton wrote:
| Code is read more often than written. Writing code that
| can be understood at a glance (by using common, well
| understood operators) optimizes for readability.
|
| I think your argument is basically "people should not
| aggressively violate the implicit bonds of interfaces",
| which is true. But that goes for all interfaces, not just
| and not in particular those around operators.
|
| We just have cases where it's common with operators
| because those are one of the few cases where we have
| _lots_ of things that meet the interface and interact
| directly as opposed to hierarchically. The same kind of
| issue comes up with co /contravariant types and
| containers sometimes, but that's less often visible to
| end developers.
| cogman10 wrote:
| I tend to agree with this. I like operator overloading
| for mathematical constructs (like complex numbers or even
| just for conversions of literal types, Imagine, for
| example, you have a gram type and a second type, if you
| said 1g / 1s you'd get 1gps, that seems reasonable)
|
| I don't like it in the example given
| for (int i : std::views::iota(0, 6) |
| std::views::filter(even) |
| std::views::transform(square))
|
| What benefit does this have over the Javay/Rusty version
| that looks like this for (int i :
| std::views::iota(0, 6) .filter(even)
| .transform(square))
|
| ?
|
| No deducing what `|` means, you are applying the filter
| then transform function against the view.
| jenadine wrote:
| Hiding the actual logic in extra boilerplate doesn't make
| it easier to read.
|
| The lines of code in the example save reader's time as
| they focus attention on the actual business logic.
|
| Yes, this assumes that operator overloading follows some
| convention, but you need to follow conventions regardless
| to make readable code.
| woooooo wrote:
| Making things explicit isn't necessarily boilerplate.
| lost_tourist wrote:
| by this reasoning should we get rid of templates/generics
| as well?
| enriquto wrote:
| YES
| JohnFen wrote:
| Operator overloading is like spicy peppers. A little bit
| can make the program better, but it's _very_ easy to use
| too much.
| cogman10 wrote:
| Unfortunately, like spicy peppers, everyone's definition
| of "too much" is different. Some people are eating ghost
| chili peppers just fine while others are struggling with
| ketchup.
| throwaway894345 wrote:
| The code isn't readable (you can't even reliably tell at a
| glance what the operator does) and it takes negligibly
| longer to write "add()" rather than "+" in your program
| (yes, 'add()' is more keystrokes and thus takes longer to
| type, but most of your program isn't addition
| instructions).
|
| I think what people should advocate is full DSL
| capabilities with some unambiguous gate syntax so people
| know precisely that `foo * bar` is not using the host
| language syntax. Overloading operators is ambiguous and
| vastly incomplete (everyone is holding up matrix math as
| the shining example for the utility of operator overloading
| and you can't even express dot product notation in
| C++!)--it's a hack at best.
| josefx wrote:
| > The code isn't readable (you can't even reliably tell
| at a glance what the operator does) and it takes
| negligibly longer to write "add()" rather than "+" in
| your program (yes, 'add()' is more keystrokes and thus
| takes longer to type, but most of your program isn't
| addition instructions).
|
| Except now you replaced + with a name that tells you just
| as much/little as + does. So you made your program
| verbose for the sake of verbosity.
| rdtsc wrote:
| Agree. It looks fun when I am writing the code and re-
| inventing abstract algebra and category theory types for
| classifying cat pictures. However then at some point I have
| to read someone else's code, even my own code weeks later and
| then I start cursing operator overloading.
| renox wrote:
| How about infix function with a prefix? That is to say #+
| would be an overloadable infix function, + would be a non
| overloadable operator.
| qalmakka wrote:
| BS. I thought that Java already demonstrated to the world how
| dumb it is to disallow operator overloading altogether.
|
| Allowing ANY operator to be overloaded was dumb, like C++
| did, where you could do batshit crazy stuff like overloading
| unary & (addressof) or the comma operator (!), or stuff like
| the assignment operator (that actually opens a parenthesis
| about how copy/move semantics in C++ are a total hack that
| completely goes OT compared to this).
|
| Sensible operator overloading makes a lot of sense,
| especially when combined with traits that clearly define what
| operations are supported and disallow arbitrary code to
| define new operators on existing types. Rust does precisely
| that, and IMHO it works great and provides a much nicer
| experience than Java's verbose mess of method chaining.
| tomcam wrote:
| > Fight me.
|
| Lunch, behind the cafeteria. Closed fists allowed, nothing
| below the belt.
| coldpie wrote:
| "Don't bring your fists to a C++ spec fight."
| variadix wrote:
| I disagree, it's heavily abused but very useful for types
| where it's obvious what the operation is (inherently
| mathematical types like vectors and matrices). I wrote a
| macro library for C that vector/matrix math in prefix
| notation with _Generic overloads and it's still too clumsy to
| get used to.
| throwaway894345 wrote:
| > where it's obvious what the operation is (inherently
| mathematical types like vectors and matrices)
|
| Considering there are like 3 different types of matrix
| multiplication operations, I don't think it's obvious at
| all. Feels like you should either use a language with
| complete support for implementing custom DSLs (that can
| express the whole domain naturally) or eschew ambiguous
| operator overloading altogether (gaining consistency and
| quality at the expense of a few keystrokes).
| suremarc wrote:
| I think we all know what someone means when they say
| "matrix multiplication". Asserting that * could mean,
| say, the Hadamard product or the tensor product is a
| reach. In practice I have never seen it mean anything
| else for matrices.
|
| DSLs just push the complexity away from the language into
| someone else's problem in a way that has much higher sum
| complexity. You're making authors of numerical libraries
| second-class citizens by doing so. For some languages
| that's probably not a bad choice (Go is one example where
| I don't feel the language is targeted at such use cases).
|
| Also, the lack of a standard interface for things like
| addition, multiplication, etc. means that mathematical
| code becomes less composable between different libraries.
| Unless everyone standardizes on the same DSL, but I find
| this an unlikely proposition, given that DSLs are far
| more opinionated than mere operator overloads.
| projektfu wrote:
| In C++, with its templates, there are only a couple
| alternatives:
|
| 1. Operator overloading
|
| 2. Operator desugaring (e.g. __subscript__(), which
| substitutes the intrinsic function for basic types, but can
| also be defined for user defined types)
|
| 3. Writing templates with weird adaptors for primitive types.
|
| Given that its design goal was to embed C, there were already
| operators that worked with various and mixed types. Adding
| (+.), etc., would have been unacceptable to the users. So, I
| think in general, for this language, it was good but,
| unfortunately, iostream made people think you should overload
| the behavioral expectation, too.
| duped wrote:
| The alternative GP is advocating for is "none of the
| above." Meaning "operators are only defined for primitive
| types" which is perfectly fine when working with C.
|
| If you wanted to use an abstraction over non primitive
| types for such things you would use a normal function.
| ksherlock wrote:
| Consider the humble + operator. In most compiled languages --
| even those that don't support operator overloading -- it is
| in fact overloaded. int + int. long + long. float + float.
| double + double. pointer + int. Would every language be
| better with it?
|
| Built in operators don't always map 1-1 to CPU instructions
| so don't appeal to that authority. There are still plenty of
| CPUs -- old and new -- without multiplication, division, or
| floating point support.
| yxre wrote:
| Why not do num1.add(num2)? Or str1.append(str2)?
|
| Like with many things in C++, its another grenade, but when
| used appropriately is pretty great
| crickey wrote:
| "Fried shrimp should be removed from the all you can eat
| chinese buffay because i cant help myself from eating at
| least 20 of them in a single sitting and now i have stomach
| cramp"
| coldpie wrote:
| The very first Hello World program anyone learning C++ will
| write uses the godawful iostream bitshifting operators! Not
| even the language's authors could help themselves eating 20
| fried shrimp on the first day the buffet was open!
| JohnFen wrote:
| This!
|
| The iostream bitshift overload was one of the first
| features of C++ that I learned to despise. I'm very happy
| that there's an alternative in the new version.
| crickey wrote:
| Agreed the gluttons
| qalmakka wrote:
| If we were set on banning all of the features C++ abused
| from new languages, we would probably end up writing just
| empty brackets.
| GuB-42 wrote:
| And even then, some Python fan will come in and this is
| what you will end up with.
|
| https://en.wikipedia.org/wiki/Whitespace_(programming_lan
| gua...
___________________________________________________________________
(page generated 2023-07-11 23:01 UTC)