As far as my experience goes, a breakpoint on a given line of code means that the first time the debugger breaks on that line, nothing of that line has been executing yet. Or, in other words, when the debugger stops at a breakpoint, it stops there before executing that line of code, not after it or in the middle of it.
And I don't think I had ever seen this view to be contraddicted (despite my experience is mostly on C++, and so that's what I've been always debugging).
However, this seems not to be the case when coroutines are involved.
Take this code from a previous question of mine (it is broken because the code has UB, but the issue I'm describing occurs before that is triggered, so it doesn't matter), a target excerpt of which is here:
class [[nodiscard]] CoroTaskSub {
…
public:
…
CoroTaskSub(auto h)
: hdl{h} {
}
…
};
CoroTaskSub coro() {
…
}
CoroTaskSub callCoro() {
std::cout << " callCoro(): CALL coro()\n";
co_await coro();
…
}
I've done the following:
- put a breakpoint at the
coutline - put a breakpoint at the
co_awaitline - run the program (at this point the debugger stops at the first of those two breakpoints)
- put a breakpoint at the line
: hdl{h} {, i.e. in the constructor of theCoroTaskSubclass¹ - continue with the program
At this point I expected that the debugger woudl break at the breakpoint on the co_await line, before anything of that line is evaluated, but instead it breaks a the breakpoint on CoroTaskSub's constructor, just like the coro() is being processed already.
That's a bit weird, no?
Do you know why that happens?
(¹) Because of how the program is structured, the control has already passed once here, upon the call callCoro() that is done in main, but I've put the breakpoint only here because I wanted to see when this constructor is called to construct coro()'s CoroTaskSub.
Unlike in a regular function, in a coroutine function, creating the return value object is the first thing that happens (well, after creating the promise and a few book-keeping things). This is before any line of code in the coroutine, before even the
initial_suspendpoint. This is done by calling the promise'sget_return_objectfunction.This is necessary because the return value object needs to be a valid object to the caller in the event that the
initial_suspendpoint suspends.