4

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.

Community
  • 1
  • 1
rubenvb
  • 74,642
  • 33
  • 187
  • 332
  • 1
    See [this answer](http://stackoverflow.com/questions/2139224/how-to-pass-objects-to-functions-in-c/2139254#2139254) for why passing objects per copy (`operator<<(T)`) usually isn't a good idea. – sbi Sep 07 '10 at 22:30
  • See http://www.templog.org for a logging library that takes optimization to the extreme. – sbi Sep 07 '10 at 22:31
  • @sbi: Yeah, I changed the arguments to const T &, but somehow that didn't get into the question. I always try to maintain const T & arguments. And thanks for the link, but it's a bit too complicated for my use. And I'm trying to learn C++ by doing as much as possible myself, and thus limiting external dependencies. – rubenvb Sep 07 '10 at 22:50
  • Did that code actually involved cakes in any way? – einpoklum Apr 19 '16 at 09:09
  • @einpoklum haha no, and I changed the name once I found out PHP of all things has a thing with cake too. For reference, I changed this to use C++11 variadic template black voodoo magic using [this](http://stackoverflow.com/q/17339789/256138) so that I can effectively remove the debug print statements completely using a simple macro definition. – rubenvb Apr 19 '16 at 12:29

7 Answers7

6

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
  • 257,169
  • 86
  • 333
  • 562
  • 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 Sep 07 '10 at 21:14
  • This should really be templated on the output type - what if you wanted to change to wcout? – Puppy Sep 07 '10 at 21:28
  • @DeadMG: The original poster explicitly uses std::cout. But give me 5 min. – Martin York Sep 07 '10 at 21:34
  • Martin: thanks for the great suggestions, you have the runner up accepted answer. – rubenvb Sep 07 '10 at 22:40
6

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.

Puppy
  • 144,682
  • 38
  • 256
  • 465
  • How will this expand to code in a release build? Is there an even better way than what I proposed? Thanks – rubenvb Sep 07 '10 at 21:33
  • 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. – Puppy Sep 07 '10 at 21:47
  • What if I did a `#define Debug(integer) nullstream`? Isn't this the fastest solution, with the lowest overhead? – rubenvb Sep 07 '10 at 22:05
  • 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. – Puppy Sep 07 '10 at 22:22
  • ... One more thing: Why can't I make the operator<< return const? – rubenvb Sep 07 '10 at 22:38
  • 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 Sep 07 '10 at 23:19
  • Regular ostream << doesn't return const, because it alters the stream state. – Puppy Sep 08 '10 at 08:47
2

You should write something like

operator<<(const T &t)

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

Andre Holzner
  • 18,333
  • 6
  • 54
  • 63
  • 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 Sep 07 '10 at 21:06
2

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;
}
1
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
  • 9,557
  • 2
  • 31
  • 39
0

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
  • 476,176
  • 80
  • 629
  • 1,111
0

...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 Michalik
  • 4,331
  • 16
  • 18