Оптимизатор решает, что все ваши оставшиеся действия не имеют никакого эффекта, и оптимизирует их.
Правильно это или нет - другое дело.
В частности:
X x;
создает пустой объект "x"
* +1007 *
звонки:
return (os << obj);
который добавляет пустой объект; компилятор замечает, что 'os' не вырос с момента последнего вызова и не обещает делать это дальше (и больше ничего не происходит), поэтому он решает, что весь бизнес является избыточным и может быть обрезан в этот момент.
В случае, если вы звоните
cout << "hehe"; // comment this and infinite loop is gone
есть дополнительная активность, поэтому оптимизатор не удаляет следующий вызов.
Полагаю, что если вы инициализировали x
чем-то непустым или выполнили любое ненулевое действие, отличное от cout << "hehe";
, рекурсия была бы запущена точно так же.