I'm not making the argument that people shouldn't use debuggers -- obviously if there's a good one that does what you need, it'd be silly not to use it. And good debuggers are great.
But what happens when you're working on a distributed system? Or multiple processes on a single system? Or...etc. etc. -- debuggers are almost always built to support a particular set of use cases for a particular set of domains. Step outside, and they're not usually as useful.
print always works. It's almost never the best. It's more flexible because it can almost always be used.
Option1: Print all of them, requires rebuild, would log 200000 lines every second. Unless i wrap the print inside conditions, requiring yet another rebuild.
Option2: Conditional breakpoint: user[i].department.balance <= expected_value. Bang, i can now inspect both the complete nested user-object, and the previous/next item in the list, other local and global variables, the call stack of how it reached there, the state of all other threads in this moment, and so on. With the really good debuggers like .NET or JVM I'm even able to rewind the execution pointer to the start of the function or hot-swap the code as it's running.
In short, a debugger allows you to see all state of the program at once, and you can retroactivelly choose what is relevant. As opposed to printf where you must select upfront. Maybe even more importantly it's lacking stack traces, unless you add a log and request-id at every function-enter/exit.
There is also the case of third party code which you can't edit to add printf to. Many environments usually by default give you pretty good context of the symbol names or even complete source code which you can step through (except c++ land where you'd need a debug-build of the lib, which isn't impossible either).
If you have some completely undocumented object oriented code with tons of relationships, peeling the state apart in a debugger is a good way to get an idea of what you even have that might be worth printing.
Lots of things but an obvious example is private member info.
Debugging via print allows you to step outside and peer in ie it is active.
Print debugging can be prone to bugs within itself which may cause additional ignorance about the potential bugs being diagnosed. How meta can you get? 8) There's also the effect of the effort of actually looking - that may or may not have an effect.
Anyway, the discussion here is largely ignorant of language and function. At the moment I spend time fiddling up Python and OpenSCAD scripts if I dig out a programming language. For me, print is really handy. For a Linux low level latency sensitive driver in highly optimised ASM n C I suspect this matter is moot.