Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

The control flow is the same; you evaluate the parameters, and then evaluate the operator. Just like any other function call, there's nothing implicit or hidden. The only difference is that you can't create other operators with the same name for different types.

And whether something is called or run inline is always decided by the compiler. Modern C doesn't promise you any relation between the way you break down your functions on your code and the actual function calls on the assembly it generates.

So, I keep seeing people complaining about overloading; always with the same reasons; that are patently not valid unless there's some implicit assumption they keep not stating. What is that assumption that breaks the equivalence between user-defined functions and operators?



Just like any other function call, there's nothing implicit or hidden.

The implicit part is the question of whether an operator is built-in or overloaded. In C, every operator is built-in, so you can look at a block of code and see that there are NO function calls in it. With something like C++, you must treat every operator like a function call.

With C, if I write:

    a += b;
I can be VERY confident that this line of code will execute in constant time. With C++ (or other operator-overloaded language), I cannot. I need to know what the types of a and b are, and I need to go look up the += operator to see what it does for these types (and this is not one universal place, it's specific to the type).

Furthermore, this may be the last line within a particular scope. With C I know that nothing else will happen, and that the control flow depends only on the surrounding scope. With C++, I don't know this! There may have been many objects created within this scope and now their destructors are firing and potentially very large trees of objects are being cleaned up and deallocated, and even slow IO operations running.


> With C++ (or other operator-overloaded language), I cannot

All programming requires people to follow reasonable conventions. In C++ if you make a dereference operator with non-constant time, or an equality operator which doesn't follow equality semantics, the programmer messed up. It's like giving a function a misleading name, like `doThis()` and it doesn't.

Note that Java is filled with these kinds of conventions, such as overloading `equals`. How can you be certain it actually obeys equality semantics? You have to trust the programmer.


If I see `x+y` in C, I know 100% that it'll be ~0-1 instructions, O(1), and will have the lowest latency & highest throughput that a thing can have, i.e. basically completely ignorable for figuring out the perf of a piece of code, or determining what complex things it may do (additionally, it'll hint that the operands are pointers or numbers). For `f(x,y)`, none of those may hold. With operator overloading, f(x,y) and x+y have the exact same amount of instantly tellable facts, i.e. none. x+y becomes just another way to do an arbitrary thing.

In C, if I'm searching for how a certain thing may be called from a given function, I only have to look for /\w\(/ and don't have to ever think about anything else.

Honestly, operator overloading isn't really that bad (especially if an IDE can highlight which ones are), but it's still a thing that can affect how one has to go about reading code that might not even use it.


However as a novice I found it unintuitive that on an embedded platform without hardware floats x/y will compile but compiles to a polyfill with quite a few instructions.


That’s the only caveat. With operator overloading, the scope for what happens on a given line of code expands dramatically. Now your entire dependency graph is part of the search space. Heck, the operator might not even terminate at all!


> That’s the only caveat.

a = b + c;

Is the addition done by itself, so it costs 1 clock cycle? Is it merged into some complex operation so the net cost is less than 1 cycle? Is it completely optimized away at compile time, so it's infinitely faster?

Does the addition trigger some trap, that will run some distant code?

Is the addition by itself? Or are there store and load instructions that can stall for way more than 1000 cycles?

I doubt you can answer any of those questions. All you and everybody else keep repeating is you can micro-optimize C better because that line, that you expect to take something from 0 to 2000 cycles is certain to not do a call and return pair, that takes less than 10 cycles. All while the alternative is almost certain to do the exact same, but you would need to check it up.

Honestly, that argument doesn't make sense; and I keep understanding it as people complaining that they want to micro-optimize a program, but don't know if it's operating on native integers or 10-dimensional hypermatrices.

At the same time, every single person that is good at micro-optimizations look at the compiled binary as a first step, because C is a high-level language that has little relation to the code the compiler actually creates.

For a long time I did just shrug it away and file those complains as "those people don't even know the language they are using". But its universality forces me to consider that there is a reason for complaining, and maybe it's worthwhile to understand. Now, given that this is all the answer I get, it seems quite likely that even the ones complaining don't consciously know what the problem is... But one thing is certain here, the people repeating that execution time is well known didn't actually practice micro-optimizations based on that fact.


Your argument boils down to this: because we cannot look at an operator and have a 100% iron-clad guarantee of the exact sequence of instructions the compiler will ultimately emit, we should throw it all away and just settle for every operator in the language potentially being a function call that might be O(1) or O(n) or even O(2^n). That's called throwing out the baby with the bathwater.

every single person that is good at micro-optimizations look at the compiled binary as a first step

That isn't an option when you're writing portable code that runs on many different platforms, some of which may not even exist at the time you're writing it. Furthermore, micro-optimization isn't the only reason operator overloading is bad. The implicit flow control dramatically inflates the search space for what every single operation can do, making all code much more complicated to inspect at a glance. This carries over to debugging, where stepping through code is much more cumbersome when each operation can involve large amounts of indirection.


> Is the addition done by itself, so it costs 1 clock cycle? Is it merged into some complex operation so the net cost is less than 1 cycle? Is it completely optimized away at compile time, so it's infinitely faster?

Those are generic instruction selection/optimization questions, which are always gonna be *additional* complexity to any and all operations everywhere. So there's still benefit in cutting down the complexity elsewhere.

> Is the addition by itself? Or are there store and load instructions that can stall for way more than 1000 cycles?

..those are questions about the loads & stores, not addition. On embedded, afaik loads & stores will be significantly closer in latency to arith too.

> At the same time, every single person that is good at micro-optimizations look at the compiled binary as a first step, because C is a high-level language that has little relation to the code the compiler actually creates.

Yes, but being able to have good intuition is still quite important, because one can think & read code much faster than compile & read assembly.

> the people repeating that execution time is well known didn't actually practice micro-optimizations based on that fact.

The question of operator overloading is mostly about reading code, not writing it. And it doesn't have to be micro-optimization either, any level of optimization will be affected by a call happening where you don't expect one (probably most importantly the kind where you scan over a piece of code to figure out if it does anything suspiciously bad (i.e. O(n^2) or excessive allocations or whatever thing may be expensive in the codebase in question) but it isn't worth the effort diving into assembly or figuring out how to get representative data for profiling the specific thing).

Or you could just be exploring a new codebase and wanting to track down where something happens, where it'd be beneficial to have to just scan through function calls and not operators.


Right, that's definitely quite a strong point against the C operator-function separation. There can be a good argument made for just not providing unavailable operations as operators. But, still, x/y won't touch any of your memory (assuming a non-broken stdlib), so you're still free to skip over it while scanning for a use-after-free or something.


User defined functions require a function call pre- and post- amble to be added to the machine instructions that execute the function behavior. Typically this consists of growing the stack, adjusting required pointers at the top and then undoing that at the end. In C the operators defined by the language implementation do not involve any adjustments to the stack frame and do not invoke a ‘call’ or jump instruction in the assembly. Once operator overloading is possible this difference immediately becomes blurred.




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

Search: