Thread: Building a small debugging tool for myself

  1. #16
    Registered User
    Join Date
    Aug 2019
    Location
    inside a singularity
    Posts
    309
    I think I got it to work, something like what I mentioned.... Turns out it's not that impossible :)

    I've been thinking really hard about this for the past few days and remember when I asked "Are the implementations of the operator overloads for basic types like int, float, char etc present in the STL in some header files or is it intrinsic to the compiler" (Post #9)? Thinking along those lines, I got it to work for the basic types.

    z_debug.h (I'm writing something called the Z_STL ;), *jokes*) :

    Code:
    #ifndef Z_DEBUG_H_INCLUDED
    #define Z_DEBUG_H_INCLUDED
    
    #include <iostream>
    #include <vector>
    #include <string>
    #include <sstream>
    #include <algorithm>
    #include <iterator>
    
    #define Debug(VarArgs...)                                                       \
    {                                                                               \
        std::string S = #VarArgs;                                                   \
        std::replace (S.begin () , S.end () , ',' , ' ');                           \
        std::stringstream SS (S); std::istream_iterator<std::string> Iterator (SS); \
        Error (Iterator , VarArgs);                                                 \
    }
    
    void Error (std::istream_iterator <std::string> Iterator) { }
    template <typename Type , typename... Args> void Error (std::istream_iterator <std::string> Iterator , Type Var , Args... ExtraArgs)
    {
        std::cerr << '\n' << *Iterator << ": " << Var << '\n';
        Error (++Iterator , ExtraArgs...);
    }
    
    template <typename T> class Class;
    
    template <typename T> std::ostream& operator << (std::ostream& Stream, const Class <T>& Output);
    
    template <typename T> class Class
    {
    private:
    
            T Variable;
            std::vector <T> VariableHistory;
    
    public:
    
         Class (const T& Other = T ());
        ~Class ();
    
        Class <T>& operator = (const T& Other);
    
        friend std::ostream& operator << <>(std::ostream& Stream , const Class <T>& Output);
    
    };
    
    template <typename T> Class <T>::Class (const T& Other)
        : Variable (Other)
        , VariableHistory ()
    {
        VariableHistory.push_back (Variable);
    }
    
    template <typename T> Class <T>::~Class ()
    { }
    
    template <typename T> Class <T>& Class <T>::operator = (const T& Other)
    {
        Variable = Other;
        VariableHistory.push_back (Variable);
        return *this;
    }
    
    template <typename T> std::ostream& operator << (std::ostream& Stream, const Class <T>& Output)
    {
        for (int i = 0; i < (int)Output.VariableHistory.size (); i++)
            Stream << "\n[" << i + 1 << "] : " << Output.VariableHistory [i];
    
        return Stream;
    }
    
    #ifdef ZDEBUG
    
    #define char    Class <char>
    #define Int     Class <int>
    #define UInt    Class <unsigned int>
    #define float   Class <float>
    #define double  Class <double>
    #define LL      Class <long long>
    #define String  Class <std::string>
    
    #else
    
    #define Int     int
    #define UInt    unsigned int
    #define LL      long long
    #define String  std::string
    
    #endif // ZDEBUG
    
    #endif // Z_DEBUG_H_INCLUDED


    DebugTest.cpp :

    Code:
    #include <iostream>
    
    #define ZDEBUG
    
    #include <z_debug.h>
    
    int main (void)
    {
        String a ("Hello");
        a = "World";
        a = "Zeus";
    
        Int b = 10;
        b = 100;
        b = 1000;
    
        float f = 5.0f;
        f = 1000.06f;
    
        Debug (a , b , f);
    
        return 0;
    }


    Output:

    Code:
    a:
    [1] : Hello
    [2] : World
    [3] : Zeus
    
    b:
    [1] : 10
    [2] : 100
    [3] : 1000
    
    f:
    [1] : 5
    [2] : 1000.06


    There's still a few problem though :(

    I cannot use int instead of Int because of the way #define works. (Writing "int main" causes the problem. How do you guys think I can fix that?

    Also, if I had pair or another vector to debug or a tuple maybe, how should I be doing the iterating and printing? Template specialization or is there another way around it?

    I still haven't figured out how I could use __LINE__ to store the line number of each assignment (as it stores the current line number and for the assignment operators, the line in the header file gets stored and not the line number from where I'm calling the overload) and I think I'll change VariableHistory to be a vector of string and store the line numbers in it (iff I can write a macro or something ... finding it hard to explain what I'm thinking so I'll leave it here but I'll see what I can do)

    TODO: Overload other operators like +=, etc to make things work smoothly even when ZDEBUG is defined.
    Last edited by Zeus_; 12-15-2019 at 02:49 AM. Reason: Removed extra spacing and added output
    "Programming today is a race between software engineers striving to build bigger and better idiot-proof programs, and the Universe trying to produce bigger and better idiots. So far, the Universe is winning." - Rick Cook, The Wizardry Compiled

  2. #17
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,213
    Quote Originally Posted by Zeus_
    Turns out it's not that impossible
    Before you jump to that conclusion:
    • Your code results in undefined behaviour: reserved words are... reserved.
    • You're not really instrumenting the built-in types; you're instrumenting a class template that you've written from scratch, and then trying to pretend that instantiations of that class templates are built-in types, and imperfectly so because you force the use of certain non-built-in type names, thereby shattering the pretense. If you minus the pretense, then of course such instrumentation is possible.
    • Even though the instrumentation is possible for class types, you're still going to run into the issue of user-defined class types: surely you cannot predefine a class template for every possible user defined type with some set of non-const non-private member functions.
    Last edited by laserlight; 12-15-2019 at 04:46 AM.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  3. #18
    Registered User
    Join Date
    Aug 2019
    Location
    inside a singularity
    Posts
    309
    Yeah, all that is there, but it was never my intention to make it work for every user-defined type. All I wanted to do was make a simple tool for myself that helps with debugging while competing in different programming contests (I've only recently started taking competitive programming seriously). There are sometimes those little errors that creep into the logic and this'll help me atleast, I think, if not anyone else, to fix it. Most of the time I'm only using int, float, long long, double for variables while practicing questions. So having it work for these is more than enough for me right now, but I'd definitely like to rewrite the code in such a way that it can work for almost every type dealing with multiple basic types (like pair dealing with an int and string maybe). If I think more sophisticatedly for a few more days and have time to experiment more, I'll try and make things work differently yielding the same behaviour.

    Also, here's the template I'll be using for contests from now:

    Code:
    // While testing code on my computer
    
    #include <iostream>
    
    #if 1
    
    #include <z_debug.h>
    
    #else
    
    #define Int     int
    #define UInt    unsigned int
    #define LL      long long
    #define String  std::string
    
    #define Debug(...)
    
    #endif
    
    // Some other macros I use like pb for push_back etc etc
    
    int main (void)
    {
        String a ("Hello");
        a = "World";
        a = "Zeus";
    
        Int b = 10;
        b = 100;
        b = 1000;
    
        float f = 5.0f;
        f = 1000.06f;
    
        Debug (a , b , f);
    
        return 0;
    }
    
    // Output (I changed a few things in z_debug.h:
    
    [FILE] : C:\Programming\CodeBlocks\Projects\Zeus\Z_Debug\DebugTest.cpp
    [DATE] : Dec 15 2019
    [TIME] : 16:52:40
    
    a:
    [1] : Hello
    [2] : World
    [3] : Zeus
    
    b:
    [1] : 10
    [2] : 100
    [3] : 1000
    
    f:
    [1] : 5
    [2] : 1000.06
    
    Debugging...
    
    // When submitting code
    
    #include <iostream>
    
    #if 0
    
    #include <z_debug.h>
    
    #else
    
    #define Int     int
    #define UInt    unsigned int
    #define LL      long long
    #define String  std::string
    
    #define Debug(...)
    
    #endif
    
    // Some other macros I use like pb for push_back etc etc
    
    int main (void)
    {
        String a ("Hello");
        a = "World";
        a = "Zeus";
    
        Int b = 10;
        b = 100;
        b = 1000;
    
        float f = 5.0f;
        f = 1000.06f;
    
        Debug (a , b , f);
    
        return 0;
    }
    EDIT: I've seen many programmers on streams not following the standard while participating in contests and just read about it on Quora. I don't think it's a bad thing to do when you're testing code yourself but what are all your opinions?
    Last edited by Zeus_; 12-15-2019 at 05:36 AM.
    "Programming today is a race between software engineers striving to build bigger and better idiot-proof programs, and the Universe trying to produce bigger and better idiots. So far, the Universe is winning." - Rick Cook, The Wizardry Compiled

  4. #19
    Registered User
    Join Date
    Aug 2019
    Location
    inside a singularity
    Posts
    309
    Hey, I'm having a lot of difficulty understanding template specialization when trying to implement something. Here's the code:

    Code:
    friend std::ostream& operator << <> (std::ostream& Stream , const Class <T>& Output);
    This would work for basic data types. But for aggregate data types, I'd like to specialize the template implementation. Something like:

    Code:
    friend std::ostream& operator << <std::pair <T1 , T2>> (std::ostream& Stream , const Class <T>& Output);
    The problem is that types T1 and T2 are not declared (and I don't think I can retrieve the types that way anyway) and I'm not able to think of a way to determine types T1 and T2. Basically, Variable will be of type of pair <int , string>, let's say, and I want to write a different operator overload for pairs. I've tried a lot of things for the past one hour and haven't been able to achieve anything.

    Can someone direct me to sites to read more about how specialized templates work or help me with the above code if you understand what I'm trying to do?

    EDIT:

    Is writing this:
    Code:
    friend std::ostream& operator << <>
    different from:

    Code:
    friend std::ostream& operator << <T>
    this?
    Last edited by Zeus_; 12-15-2019 at 11:11 AM.
    "Programming today is a race between software engineers striving to build bigger and better idiot-proof programs, and the Universe trying to produce bigger and better idiots. So far, the Universe is winning." - Rick Cook, The Wizardry Compiled

  5. #20
    Registered User
    Join Date
    Aug 2019
    Location
    inside a singularity
    Posts
    309
    EDIT 2:

    Nevermind, I got it to work Templates look stupid when you code them but it's a very very smart way and the tiniest of mistake screws them up. I realised what I was doing brutally wrong and it was a very small mistake. Fixed it now, will post new code that works for pairs and vectors (and maybe maps if I can complete it) once I clean up the mess I've made by testing so many different things. I changed the way I was using ostream& operator << <> because it was just weird to look at and made it hard for me to understand what was going on.
    "Programming today is a race between software engineers striving to build bigger and better idiot-proof programs, and the Universe trying to produce bigger and better idiots. So far, the Universe is winning." - Rick Cook, The Wizardry Compiled

  6. #21
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,213
    Quote Originally Posted by Zeus_
    But for aggregate data types, I'd like to specialize the template implementation. Something like:
    You're missing the template keyword and template parameter declaration, but if you add them in, you would get function template partial soecialisation, which is not allowed. Luckily, in this case what you want to specialise is a function parameter, so you can just overload the function template instead.

    Quote Originally Posted by Zeus_
    I've seen many programmers on streams not following the standard while participating in contests and just read about it on Quora. I don't think it's a bad thing to do when you're testing code yourself but what are all your opinions?
    If you're not following the standard because you're simply using something outside of the scope of the standard (e.g., OS-specific stuff, documented language extensions) then that's fine, but you should be aware that your code is then non-portable unless you take steps to say, make it cross platform (in the case of OS-specific stuff). But if you're not following the standard because you're relying on undefined behaviour that your compiler has not documented as being "okay", then you risk bugs from things like changing the compiler options or upgrading the compiler (and of course from switching to another compiler or compiling on a different platform).

    That said, you should also be aware that competitive programming is a "sport" unlike programming to build things that are commercially viable or to support a business or some other long term team. The aim is to win the competition, not to write maintainable code that not only works but can continue to work in the face of changing requirements plus the team that was around while it was written being long gone.
    Last edited by laserlight; 12-15-2019 at 08:35 PM.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  7. #22
    Registered User
    Join Date
    Aug 2019
    Location
    inside a singularity
    Posts
    309
    Hey, found some time and implemented a few more things today. Okay, so I know that @laserlight mentioned that my code will have undefined behaviour i.e. behaviour that's not been accounted by the standard and that I should implement code in a better way to support user defined types too. I've been thinking about it for a while now and I've come up with a few ideas but I'll let that stay for now. I have a working prototype right now that isn't in its best shape but here's the code:

    Code:
    #ifndef Z_DEBUG_H_INCLUDED
    #define Z_DEBUG_H_INCLUDED
    
    #include <bits/stdc++.h>
    
    #define Debug(VarArgs...)                                                         \
    {                                                                                 \
        std::string Str = #VarArgs;                                                   \
        std::replace (Str.begin () , Str.end () , ',' , ' ');                         \
        Str += " Debugging...";                                                       \
        std::stringstream SS (Str); std::istream_iterator<std::string> Iterator (SS); \
                                                                                      \
        std::cerr << std::endl << std::endl                                           \
                  << "[FILE] : " << __FILE__ << '\n'                                  \
                  << "[DATE] : " << __DATE__ << '\n'                                  \
                  << "[TIME] : " << __TIME__ << '\n';                                 \
                                                                                      \
        Error (Iterator , VarArgs);                                                   \
    }
    
    void Error (std::istream_iterator <std::string> Iterator)
    { std::cerr << '\n' << *Iterator; }
    
    template <typename Type , typename... Args> void Error (std::istream_iterator <std::string> Iterator , const Type& Var , Args... ExtraArgs)
    {
        std::cerr << "\nValues of [" << *Iterator << "] : " << Var << '\n';
        Error (++Iterator , ExtraArgs...);
    }
    
    template <typename T> class Debugger
    {
    private:
    
            T Variable;
            std::vector <T> VariableHistory;
    
    public:
    
         Debugger ();
         Debugger (const Debugger <T>& Other);
         Debugger (const T& Other);
        ~Debugger ();
    
        operator T () const;
    
        Debugger <T>& operator =   (const Debugger <T>& Other);
        Debugger <T>& operator +=  (const Debugger <T>& Other);
        Debugger <T>& operator -=  (const Debugger <T>& Other);
        Debugger <T>& operator *=  (const Debugger <T>& Other);
        Debugger <T>& operator /=  (const Debugger <T>& Other);
        Debugger <T>& operator &=  (const Debugger <T>& Other);
        Debugger <T>& operator |=  (const Debugger <T>& Other);
        Debugger <T>& operator %=  (const Debugger <T>& Other);
        Debugger <T>& operator ^=  (const Debugger <T>& Other);
        Debugger <T>& operator <<= (const Debugger <T>& Other);
        Debugger <T>& operator >>= (const Debugger <T>& Other);
    
        Debugger <T>& operator ++ ();
        Debugger <T>& operator -- ();
        Debugger <T>  operator ++ (int);
        Debugger <T>  operator -- (int);
    
        template <typename Ty>                 friend std::istream& operator >> (std::istream& Stream ,       Debugger <Ty>& Input);
        template <typename Ty>                 friend std::ostream& operator << (std::ostream& Stream , const Debugger <Ty>& Output);
        template <typename Tp1 , typename Tp2> friend std::ostream& operator << (std::ostream& Stream , const Debugger <std::pair <Tp1 , Tp2>>& Output);
    };
    
    template <typename T> Debugger <T>::Debugger ()
        : Variable ()
        , VariableHistory ({Variable})
    { }
    
    template <typename T> Debugger <T>::Debugger (const Debugger <T>& Other)
        : Variable (Other.Variable)
        , VariableHistory (Other.VariableHistory)
    { }
    
    template <typename T> Debugger <T>::Debugger (const T& Other)
        : Variable (Other)
        , VariableHistory ({Variable})
    { }
    
    template <typename T> Debugger <T>::~Debugger ()
    { }
    
    template <typename T> Debugger <T>::operator T () const
    { return Variable; }
    
    template <typename T> Debugger <T>& Debugger <T>::operator = (const Debugger <T>& Other)
    {
        Variable = Other.Variable;
        VariableHistory.push_back (Variable);
        return *this;
    }
    
    template <typename T> Debugger <T>& Debugger <T>::operator += (const Debugger <T>& Other)
    {
        Variable += Other.Variable;
        VariableHistory.push_back (Variable);
        return *this;
    }
    
    template <typename T> Debugger <T>& Debugger <T>::operator -= (const Debugger <T>& Other)
    {
        Variable -= Other.Variable;
        VariableHistory.push_back (Variable);
        return *this;
    }
    
    template <typename T> Debugger <T>& Debugger <T>::operator *= (const Debugger <T>& Other)
    {
        Variable *= Other.Variable;
        VariableHistory.push_back (Variable);
        return *this;
    }
    
    template <typename T> Debugger <T>& Debugger <T>::operator /= (const Debugger <T>& Other)
    {
        Variable /= Other.Variable;
        VariableHistory.push_back (Variable);
        return *this;
    }
    
    template <typename T> Debugger <T>& Debugger <T>::operator &= (const Debugger <T>& Other)
    {
        Variable &= Other.Variable;
        VariableHistory.push_back (Variable);
        return *this;
    }
    
    template <typename T> Debugger <T>& Debugger <T>::operator |= (const Debugger <T>& Other)
    {
        Variable |= Other.Variable;
        VariableHistory.push_back (Variable);
        return *this;
    }
    
    template <typename T> Debugger <T>& Debugger <T>::operator %= (const Debugger <T>& Other)
    {
        Variable %= Other.Variable;
        VariableHistory.push_back (Variable);
        return *this;
    }
    
    template <typename T> Debugger <T>& Debugger <T>::operator ^= (const Debugger <T>& Other)
    {
        Variable ^= Other.Variable;
        VariableHistory.push_back (Variable);
        return *this;
    }
    
    template <typename T> Debugger <T>& Debugger <T>::operator <<= (const Debugger <T>& Other)
    {
        Variable <<= Other.Variable;
        VariableHistory.push_back (Variable);
        return *this;
    }
    
    template <typename T> Debugger <T>& Debugger <T>::operator >>= (const Debugger <T>& Other)
    {
        Variable >>= Other.Variable;
        VariableHistory.push_back (Variable);
        return *this;
    }
    
    template <typename T> Debugger <T>& Debugger <T>::operator ++ ()
    {
        Variable++;
        VariableHistory.push_back (Variable);
        return *this;
    }
    
    template <typename T> Debugger <T>& Debugger <T>::operator -- ()
    {
        Variable--;
        VariableHistory.push_back (Variable);
        return *this;
    }
    
    template <typename T> Debugger <T> Debugger <T>::operator ++ (int)
    {
        Debugger <T> Copy (*this);
        Variable++;
        VariableHistory.push_back (Variable);
        return Copy;
    }
    
    template <typename T> Debugger <T> Debugger <T>::operator -- (int)
    {
        Debugger <T> Copy (*this);
        Variable--;
        VariableHistory.push_back (Variable);
        return Copy;
    }
    
    template <typename Ty> std::istream& operator >> (std::istream& Stream, Debugger <Ty>& Input)
    {
        Stream >> Input.Variable;
        Input.VariableHistory.push_back (Input.Variable);
        return Stream;
    }
    
    template <typename Ty> std::ostream& operator << (std::ostream& Stream, const Debugger <Ty>& Output)
    {
        Stream << '\n';
        for (size_t i = 0; i < Output.VariableHistory.size (); i++)
            Stream << "\n" << Output.VariableHistory [i];
        return Stream;
    }
    
    template <typename Tp1 , typename Tp2> std::ostream& operator << (std::ostream& Stream , const Debugger <std::pair <Tp1 , Tp2>>& Output)
    {
        Stream << '\n';
        for (size_t i = 0; i < Output.VariableHistory.size (); i++)
            Stream << "\n{" << Output.VariableHistory[i].first << " , " << Output.VariableHistory[i].second << "}";
        return Stream;
    }
    
    template <typename Tp1 , typename Tp2> std::ostream& operator << (std::ostream& Stream , const std::pair <Tp1 , Tp2>& Pair)
    {
        Stream << "{" << Pair.first << " , " << Pair.second << "}";
        return Stream;
    }
    
    #endif // Z_DEBUG_H_INCLUDED
    PS: Ignore that code for Pair for now.

    And my code template:

    Code:
    #include <bits/stdc++.h>
    
    #if 0
    
        #include "z_debug.h"
    
        #define char    Debugger <char>
        #define int32   Debugger <int>
        #define uint32  Debugger <unsigned int>
        #define int64   Debugger <long long>
        #define uint64  Debugger <unsigned long long>
        #define size_t  Debugger <size_t>
        #define float   Debugger <float>
        #define double  Debugger <double>
    
    #else
    
        #define int32   int
        #define uint32  unsigned int
        #define int64   long long
        #define uint64  unsigned long long
    
        #define Debug(...)
    
    #endif
    
    using namespace std;
    
    typedef long long ll;
    typedef unsigned long long ull;
    
    // A lot of other things but no point posting it....
    
    int main (void)
    {
          return 0;
    }
    Well, it's been holding up fine for the few questions I've tried this template with although I've started to feel the problems that @laserlight may or may not have mentioned about. It is not as versatile as it should be (I will think of ways to better it). However, this is the only way I've come up with to show a variables' history but that doesn't mean I'm not going to be thinking of a better way.

    Also, @laserlight, a question. Let's say I have the following code. I obviously don't want to be typecasting Debugger <T> to T explicitly when using the STL as that's unnatural. I want to make Debugger <T> behave exactly like T everywhere except for in Assignment operations for which I've provided operator overloads. Sure, there should be a better way to just write "min ((int)minimum , arr[i + k - 1] - arr[i])" as "min (minimum , arr[i + k - 1] - arr[i])" but idk how to do so and hence, your help.
    Code:
    int main (void)
    {
        cout.sync_with_stdio (false);
        cin.tie (nullptr);
        cout.precision (10);
        cout << fixed;
    
        int32 k = 3;
        vector <int> arr {10,100,300,200,1000,100,85};
    
        int32 minimum = INT_MAX;
        sort (arr.begin () , arr.end ());
        for (int i = 0; i < arr.size () - k + 1; i++)
            minimum = min ((int)minimum , arr[i + k - 1] - arr[i]);
    
        Debug(k , minimum);
    
        return 0;
    }
    Thanks!

    Note: I've learnt templates and most of modern c++ myself through online sources and e-books so I may have gaps in knowledge that is probably shown in the code. Apologies for that.
    Last edited by Zeus_; 12-29-2019 at 10:54 AM.
    "Programming today is a race between software engineers striving to build bigger and better idiot-proof programs, and the Universe trying to produce bigger and better idiots. So far, the Universe is winning." - Rick Cook, The Wizardry Compiled

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Threads debugging graohical tool
    By bremenpl in forum C Programming
    Replies: 2
    Last Post: 08-03-2016, 03:53 AM
  2. A small problem with a small program
    By Wetling in forum C Programming
    Replies: 7
    Last Post: 03-25-2002, 09:45 PM
  3. Im in need of a special debugging tool!
    By ref in forum C++ Programming
    Replies: 2
    Last Post: 10-29-2001, 08:18 AM
  4. I need a special debugging Tool!
    By ref in forum Windows Programming
    Replies: 0
    Last Post: 10-29-2001, 03:06 AM

Tags for this Thread