views:

86

answers:

7

Hi,

I'm trying to create a simple qDebug-like class I can use to output debug messages in debug mode, dependant on some debug level passed when calling the app. I liked the ease of use of the QDebug class (which could be used as a std::cerr and would disappear when compiling in release mode). I have this so far:

#ifdef DEBUG
    static int CAKE_DebugLevel;

    class Debug
    {
        Debug( int level ) : m_output( level <= CAKE_DebugLevel ) {}

        template<typename T>
        Debug& operator<<( T )
        {
            if( m_output )
            {
                std::cout << T;
                return *this;
            }
            else
                return *this;
        }
    private:
        bool m_output;
    };
#else // release mode compile
    #define Debug nullstream
#endif // DEBUG

I think a nullstream-kind of thing would be best for the release mode define:

class nullstream{};

template <typename T>
nullstream& operator<<(nullstream& ns, T)
{
    return ns;
}

In main I have for the moment:

#include "Debug.h"

#include <iostream>

int main()
{
    Debug(0) << "Running debug version of CAKE." << std::endl;
}

Which is giving the following error with gcc 4.5.1:

In member function 'Debug& CAKE_Debug::operator<<(T)': expected primary-expression before ';' token (points at the line std::cout << T;)

In function 'int main(int, char*, char*)': expected unqualified-id before '<<' token

I'm sure it's something simple, but all the general template info I've found turns up nothing. Thanks for any help!

If you need more info, please ask.

+1  A: 
template<typename T>
Debug& operator<<( T )

This signature is wrong; you've got a type for the argument to <<, but not a name for it. You'll then want to use the variable name instead of T in std::cout << T;

David Seiler
+2  A: 

You should write something like

operator<<(const T &t)

and use t instead of T (which is the type) inside this method.

Andre Holzner
Dang, stupid compiler error threw me off... This fixes this error, but there is still a problem in main, due to `no match for operator<<(...) in class Debug...`. I get this with string, const char* (as in my example) and integers :(. Thanks
rubenvb
+2  A: 

The error is in here:

template<typename T>
Debug& operator<<( T )
{
    if ( m_output )
    {
        std::cout << T;
        return *this;
    }
    else
        return *this;
}

You cannot output a type, you have to output an object. Correct code:

template<typename T>
Debug& operator<<( T value)
{
    if ( m_output )
    {
        std::cout << value;
    }
    return *this;
}
Vlad Lazarenko
A: 

The problem with using cout << T; has already been pointed out (you need to supply an object instance, whereas T is a type).

When you fix that, you'll run into at least one more problem: Debug(0) creates a temporary object. To pass that as a parameter to your nullstream operator<<, it'll need to take a nullstream const & instead of a nullstream & as its parameter.

Jerry Coffin
+2  A: 

Others have pointed out the error with normal objects.

template<typename T> 
Debug& operator<<(T const& value)
{
    if ( m_output )  
    {  
        std::cout << value;  
    }  
    return *this;  
}

But you also need a way to output std::endl and the other manipulators. As the template above will not handle these correctly. For this you will need an operator that handles functors that manipulate streams (ie all the iomanipulators (like std::endl)).

// Use a typedef to make the code readable.
// This is a function pointer that takes a stream as input and returns the stream.
// This covers functions like std::endl
typedef std::ostream& (*STRFUNC)(std::ostream&);

D& operator<<(STRFUNC func)  // Inside the class
{
    if ( m_output )  
    {  
        // Apply the function
        func(std::cout);
    }
    // But return the debug object
    return *this;
}

Implementation that handles both normal and wide streams:

#include <iostream>

#if defined(_DEBUG)
#define DEBUG_LOG_TEST_CONDITION        output
#define DEBUG_LOG_DEBUG_TEST_LEVEL(v)   (v) <= DebugCake::DebugLevel
#else
#define DEBUG_LOG_TEST_CONDITION        false
#define DEBUG_LOG_DEBUG_TEST_LEVEL(v)   false
#endif

template<typename C,typename T = std::char_traits<C> >
struct DInfo
{
    typedef std::basic_ostream<C,T>& (*StrFunc)(std::basic_ostream<C,T>&);
    static std::basic_ostream<C,T>& getDefault();
};

struct DebugCake
{
    static int DebugLevel;
};
template<typename C,typename T = std::char_traits<C> >
struct D
{

    D(int level)
        :stream(DInfo<C,T>::getDefault())
        ,output( DEBUG_LOG_DEBUG_TEST_LEVEL(level) )
    {}
    D(int level, std::basic_ostream<C,T>& s)
        :stream(s)
        ,output( DEBUG_LOG_DEBUG_TEST_LEVEL(level) )
    {}

    template<typename V>
    D& operator<<(V const& value)
    {
        // In release this is optimised away as it is always false
        if (DEBUG_LOG_TEST_CONDITION)
        {
            stream << value;
        }
        return *this;
    }

    D& operator<<(typename DInfo<C,T>::StrFunc func)
    {
        // In release this is optimised away as it is always false
        if (DEBUG_LOG_TEST_CONDITION)
        {
            func(stream);
        }
        return *this;
    }
    private:
       std::basic_ostream<C,T>&  stream;
       bool                      output;

};

template<>
std::ostream&  DInfo<char,std::char_traits<char>       >::getDefault()
{return std::cout; }

template<>
std::wostream& DInfo<wchar_t,std::char_traits<wchar_t> >::getDefault()
{return std::wcout; }

typedef D<char>    Debug;
typedef D<wchar_t> WDebug;
int DebugCake::DebugLevel = 4;


int main()
{
    Debug   debug(1);

    debug << "Plop" << std::endl;

    WDebug  debugWide(2);
    debugWide << L"WIDE" << std::endl;
}
Martin York
That's a perfect futureproof answer (see my comment on Andre's answer). How would I go about this, because I truly have no idea :(. Can it be templated as well (actually all I need is std::endl, but to be foreseeing would be better, right) ? Thanks
rubenvb
This should really be templated on the output type - what if you wanted to change to wcout?
DeadMG
@DeadMG: The original poster explicitly uses std::cout. But give me 5 min.
Martin York
Martin: thanks for the great suggestions, you have the runner up accepted answer.
rubenvb
+1  A: 

Your nullstream class has no integral constructor. This means that when you #define Debug nullstream, the compiler can't recognize Debug(0) - that makes no sense. Debug is not a macro that takes arguments, and if you substitute with nullstream, nullstream has no constructor that takes arguments. #define used this way is oh so wrong. You should have something like this:

#ifdef _DEBUG
static int CAKE_Debuglevel;
#endif

class Debug
{
    Debug( int level ) 
#ifdef _DEBUG
    : m_output( level <= CAKE_DebugLevel ) 
#endif
        {}

    template<typename T>
    Debug& operator<<( T t)
    {
        #ifdef _DEBUG
        if( m_output )
        {
            std::cout << t;
            return *this;
        }
        else
        #endif
            return *this;
    }
private:
#ifdef _DEBUG
    bool m_output;
#endif
};

Now your class really will look and act the same in any environment but only output if _DEBUG is defined. I also fixed the bug where you tried to output a type.

DeadMG
How will this expand to code in a release build? Is there an even better way than what I proposed? Thanks
rubenvb
In a release build, it still offers the same interface (critical) but none of the constructors or operators do anything. This means by far and away that you will have no nasty surprises about what is happening, which is what you would get with #define Debug nullstream.
DeadMG
What if I did a `#define Debug(integer) nullstream`? Isn't this the fastest solution, with the lowest overhead?
rubenvb
No? nullstream doesn't do anything that my Debug doesn't - I just made it so that you don't horrendously screw up with the terrible macro system that's totally redundant in this case. You don't need nullstream and it's hideously bad practice to #define because you can. Your nullstream is the same as my Debug in release mode. My code is cleaner and results in the exact same code.
DeadMG
OK, I see now, Thanks
rubenvb
... One more thing: Why can't I make the operator<< return const?
rubenvb
That's not a good idea. Code should not be conditional on a macro. You should use the conditional macro (_DEBGU) to define the appropriate code macros that are then put in the source. Thus the source reads the same at all times and all the conditional code is grouped together in the same area.
Martin York
Regular ostream << doesn't return const, because it alters the stream state.
DeadMG
A: 

...which will result in an l/rvalue problem. Just follow the std::cout pattern, if you want a global Debug output object. Otherwise, say:

Debug tDbg(tLevel);
tDbg << "Message" << std::endl;
paul_71
This I do not understand at all...
rubenvb