Without using a virtual machine, you always face the same problem: many functions that you can use in the operating system are specific to this operating system.
This is because no one ever seriously cared to determine only a way to do the same, mainly because DirectX10, for example, is an advantage over other operating systems.
I would say it's pretty easy to write a cross-platform application that can run on MacOSX and Linux just because you can take advantage of many things that can work on both machines (think of X11 or GTK), and both of them are Unix under the hood. Usually, with some effort, you can make your programs work also under Windows (possibly with MinGW or Cygwin), also if some functions are not compatible.
In addition, different operating systems have practically different implementations for the same things (think about sockets, an io-system, graphics, audio, etc.), so this makes it impossible to record only a version that works everywhere: you are forced to write different version for any OS on which you plan to release your program.
The short answer is: no, you cannot live without the VM language, unless you limit the capabilities of your program to a small set of common functions.
(I assume that we are talking about C / C ++ only because usually only one group of people controls other languages, each of which takes care to free it as much as possible cross-platform)
In fact, I see nothing wrong with relying on a virtual machine. The computer is now able to run virtual machines without problems at high speed. Then, having something that takes care of placing the abstraction layer between you and the OS will allow you to do advanced things, just knowing how to do it. I think the trade-off between speed like Java is widely acceptable .. they just did what you would need to do when trying to write a complex cross-platform application.
Just a fact: it really hurts me when I try to port a virtual machine that I wrote from Unix (Linux / OSX) on Windows because of the freopen function. Unfortunately, Windows controls threads differently from other OSs, so MinGW lacked this feature, and I had to find a way to solve this problem.