How about this? You'd have to check that it actually optimises to nothing in release:
#ifdef NDEBUG
class DebugStream {};
template <typename T>
DebugStream &operator<<(DebugStream &s, T) { return s; }
#else
typedef ostream DebugStream;
#endif
You will have to pass the debug stream object as a DebugStream&, not as an ostream&, since in release builds it isn't one. This is an advantage, since if your debug stream isn't an ostream, that means you don't incur the usual runtime penalty of a null stream that supports the ostream interface (virtual functions that actually get called but do nothing).
Warning: I just made this up, normally I would do something similar to Neil's answer - have a macro meaning "only do this in debug builds", so that it is explicit in the source what is debugging code, and what isn't. Some things I don't actually want to abstract.
Neil's macro also has the property that it absolutely, definitely, doesn't evaluate its arguments in release. In contrast, even with my template inlined, you will find that sometimes:
debug << someFunction() << "\n";
cannot be optimised to nothing, because the compiler doesn't necessarily know that someFunction()
has no side-effects. Of course if someFunction()
does have side effects then you might want it to be called in release builds, but that's a peculiar mixing of logging and functional code.