Eternal Dreamer
Eternal Dreamer

Reputation: 474

Ignore the use of a C++ function during compilation having operator<<

I'm developing an API looking like GLib logging.

My API can also disable these functions when the flag -DDEBUG isn't passed to GCC.

That means, there are no residues in the binary, all these debug functions including their parameters aren't present in the executable if this flag isn't passed.

It works perfectly in C, because it's macro functions I can easily replace ;

Example in C

debug.h

#include <stdio.h>
#include <stdlib.h>

#include "term.h"

/**
 * @brief Print something in debug mode
 * @details No loglevel/context
 * @param format
 * @param ... Parameters for format
 */
#define printf_debug(fmt, ...)

#define LOG_NONE 0
#define LOG_FATAL 1
#define LOG_ERROR 2
#define LOG_WARNING 3
#define LOG_INFO 4
#define LOG_DEBUG 5
#define LOG_TRACE 6

#if (defined(DEBUG))
#undef printf_debug

#define printf_debug(format, ...) \
    fprintf(stderr, "        " format "\n", __FILE__, __func__, __LINE__, ##__VA_ARGS__)
#endif // (defined(DEBUG))

main.c

#include <debug/debug.h>

int main()
{
    printf_debug("Hello world !");      // Show in any debug context

    return 0;
}

If I compile without the flag -DDEBUG, neither my macro function printf_debug() nor the string "Hello" remains.


I would have like to do the same in C++ with the << operator (such as std::cout works), but I can't manage << operator like I manage parameters in C.

Here's what I have tried ;

debug.hpp

#include <iostream>

#define LOG_UNDEFINED (-1)
#define LOG_NONE 0
#define LOG_FATAL 1
#define LOG_ERROR 2
#define LOG_WARNING 3
#define LOG_INFO 4
#define LOG_DEBUG 5
#define LOG_TRACE 6

namespace debug
{
#ifdef DEBUG
    class debug_cout
        : public std::ostream
    {
    public:
        debug_cout(std::streambuf *sbuf)
            : std::ios(sbuf), std::ostream(sbuf)
        {}
        ~debug_cout() {}
    };

    debug_cout cout(int l) {
        static int level = l;
        return debug_cout(std::cout.rdbuf());
    }
#else
    class debug_cout
        : public std::ostream
    {
    public:
        debug_cout() {}
        ~debug_cout() {}
    };

    debug_cout cout(int l) {
        return debug_cout();
    }
#endif
}

main.cpp

#include <debug/debug.hpp>


int main() {
    debug::cout(LOG_ERROR) << "Hello " << 3 << std::endl;

    return 0;
}

It compiles correctly this way ; Using -DDEBUG flag, it outputs ;

Hello 3

Without -DDEBUG, it outputs nothing, but there are residues in the executable. I see using objdump my functions debug::cout() remains and is called.

I have also tried using GCC optimization flag -O3.


So my question ; Is it possible to ignore these instructions in C++ ? The ones having << operator.

Note: It's not an issue if I can't use namespace

Note 2: When I talk about residues I mean my function debug::cout() remains in the executable in C++ and is executed (and it just does nothing).

Upvotes: 1

Views: 112

Answers (3)

Igor G
Igor G

Reputation: 2471

Is it possible to ignore these instructions in C++

Yes. if constexpr can eliminate entire branches of code:

#ifdef DEBUG
#  define DEBUG_COUT(level)   if constexpr (0) {} else real_stream{level}
#else
#  define DEBUG_COUT(level)   if constexpr (1) {} else null_stream{level}
#endif

And use it like this:

DEBUG_COUT(5) << "Hello, " << 5 << " Worlds\n";

Live example: https://godbolt.org/z/7j6drn9ec

P.S. Different definitions of the same class depending on some macro? That's a recipe for wonderful bugs in case of linking wrong library/object. Don't do that.

Upvotes: 1

Ted Lyngmo
Ted Lyngmo

Reputation: 117633

Your non-logging debug_cout shouldn't inherit from std::ostream. Instead implement empty functions for streaming that does nothing. That should be easy for the compiler to optimize away:

class debug_cout {
public:
    template <class T>
        requires requires(T t) { std::cout << t; }
    debug_cout& operator<<(T&&) {
        return *this;
    }

    debug_cout& operator<<(std::ios_base& (*)(std::ios_base&)) {
        return *this;
    }

    debug_cout& operator<<(std::ios& (*)(std::ios&)) { return *this; }

    debug_cout& operator<<(std::ostream& (*)(std::ostream&)) {
        return *this;
    }
};

Demo

Upvotes: 2

Useless
Useless

Reputation: 67782

#else
    class debug_cout
        : public std::ostream

Your NDEBUG dummy object is an ostream. That's not nothing!

Even if the streambuf isn't connected to anything, that can change (and must therefore be checked) at runtime. You already know this, because you're setting the same streambuf up at runtime in the DEBUG branch.

You want a type that does nothing, so just write that:

#else
    class debug_cout {};

and you want it to work with stream insertion syntax, so write that:

    template <class T>
    debug_cout& operator<<(debug_cout& s, T&&)
    {
        return s;
    }

Upvotes: 0

Related Questions