Hacker News new | past | comments | ask | show | jobs | submit login

Honestly I can't think of many notable abuses of operator overloading in C++.

The worst is probably the original idea to overload the bit shift operators for stream I/O, something which never really caught on outside the standard library.




Agree. Meanwhile there are flagship C++ libraries like Eigen where it's indispensable. C++ has mistakes but operator overloading isn't one of them.


I remember a university project where I used the >> (and <<) operators to "send" data between services.

The code was a simulation of a parallel system with multiple services that sent messages between them, or something like that. Instead of using serviceA.send("hello",serviceB) you had something like serviceA >> "hello" >> serviceB.

I really loved that (my classmates not so much)


Reminds me of the ChucK operator.

  SinOsc b => Gain g => BiQuad f => dac;
https://chuck.stanford.edu/doc/language/oper.html#chuck


I did something similar as a student, making my own exception class with std::ostream:

throw exceptionC() << "error code: " << t;

I often found myself having to format error strings for exceptions, so I thought I could just do it like cout in one line. I know now this is bad for i18n strings.



Why would you prefer that over:

  throw exceptionC("error code: ", t);
?

Ever since perfect forwarding I've been using this pattern to get out of arrow hell:

  template <typename Arg, typename...Args>
  string to_string(Arg &&arg, Args &&...args) {
    stringstream buf;
    buf << arg;
    ((buf << std::forward<Args>(args)), ...);
    return buf.str();
  }


There could be a couple of examples in Boost like Boost Spirit [0]. Qt had some like putting stuff in containers with bitshift operator. There was a GUI toolkit that used + operator to put widgets on a window.

[0] https://www.boost.org/doc/libs/1_78_0/libs/spirit/doc/html/s...


I've seen operator* overloaded to return RAII guards, including ones for RCU, and operator() overloaded for so many things that should just be lambdas it's hilarious


Yeah the () overload was for functors, the old school way of doing closures before lambdas.


It was certainly how I learned to do it, but lambdas have been around for over a decade and I still see people writing functors. The only use case I've seen where it made sense is for things like coroutines.


For std::visit() (std::variant.visit in c++26 I see) which is the visitor pattern, you can use a functor with multiple visit types or roll your own overload() template to merge multiple lambdas into one class.

https://en.cppreference.com/w/cpp/utility/variant/visit

https://en.cppreference.com/w/cpp/utility/variant/visit2


But why would I want to do this instead of just having multiple lambdas that capture the same values by reference, or using shared_ptr and synchronization? I can see lifetimes of the data being an issue, but you shouldn't be calling std::visit in a way where that could cause a problem without synchronization anyway.


Lambdas these days cover 99% of the needs for custom function objects. But for that 1% it is useful to be able to have full control of your closure.

For example how would you implement std::function without overloaded operator()?

Also lambdas are defined in term of structs with overloaded operator ().

Without overloading, the standard could still ad-hoc define the specifications of lambdas and std::function, std:: ref, etc, but the language would be worse off.


If you want an instance with a destructor (e.g. print "this was called X times"), you need a class.


That example seems contrived. Why would I want to know something was called n-times? Even for benchmarking I would just capture an integer and increment it.

To be mildly pedantic, printing in a destructor is horrible practice because stdout/stderr might be pipes or sockets and writing might fail. It's a really bad idea to do anything in a destructor that effects anything but the class being destroyed for those reasons, and when you get an error, it shows up as an opaque exception or trace or hang at the end of a scope instead of where it actually mattered.


I actually ran into that at work a few days ago. I wanted to provide a callback that accumulated stuff, and check that the total was equal to what was expected, or crash the program otherwise (fail fast). I could have equivalently written the output of the test to a capture-by-ref variable and checked it outside if I really wanted to.


> to overload the bit shift operators for stream I/O

MFC extends this idea to network programming, allowing the use of shift operators to send and receive data.


MFC also has CComPtrBase which uses & to represent pointer lifetimes to COM objects such as while(pEnum->Next(1, &pFilter, &cFetched) == S_OK). Especially fun when debugging DirectShow filtergraphs someone made in the UI completely. There is more of an explanation here: https://devblogs.microsoft.com/oldnewthing/20221010-00/?p=10...


The soon to be standard C++26 Reflection library?


2126 that is?


operator/ for concatenating path components in std::filesystem


Continuation passing monads form the basis of a perfectly valid and usable software architecture and programming pattern.

In the case of ostream and operator<<, this pattern reduces the number of intermediate objects that would otherwise be constructed.

If you object to iostream on religious or stylistic grounds, there's always fmt which is more like Go or Python string interpolation.[0]

0. https://fmt.dev


> If you object to iostream on religious or stylistic grounds,

No I object to it on usability grounds. It took more than 30 years for the committee to admit it but C++ now has typesafe std::print/std::format finally, after admonishing programmers for using `printf` in C.


C++ only acquired the capabilities to have a statically type safe format string only recently.

Iostreams have a lot of issues, but overloading<< is a very minor one.


But it is achievable without operator overloading, isn't it? There is also a reason why format strings seem to be more popular among the crowd.


Streams have numerous design problems (e.g. representation is managed by the stream, not the thing you’re printing) making the shift operator syntax the least of the problems.


Which part of operator<< involves continuations, let alone continuation passing monads?




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: