Print class identifier if it is available

Viewed 104

The goal is to create a (macro?) that would print a unique identifier per class instance (for example, the this pointer) when it is placed in a class function, and nothing (or something else) when used in a 'normal' function. Preferably without adding anything to the class, but if that is the only way to do it, I'll take it.

Here is my attempt, however it does not like the use of this at all (replacing it with (void)1 makes it work, but I want a unique identifier per class instance. On gcc this errors out like: error: invalid use of ‘this’ in non-member function, and msvc has similar errors: error C2355: 'this': can only be referenced inside non-static member functions or non-static data member initializers

non-working code:

#include <iostream>

class _detect_class_ {};
typedef int _class_sentinel_;

#define THIS_PTR std::is_same<_detect_class_, _class_sentinel_>::value ? (void*)this : nullptr
#define TRACK_THIS_OBJECT typedef _detect_class_ _class_sentinel_;

struct thisptr
{
    void* Ptr;
    thisptr(void* ptr): Ptr(ptr){;}
};

std::ostream& operator<<(std::ostream& strm, const thisptr& p)
{
    if (p.Ptr)
        strm << "Object:" << p.Ptr;
    else
        strm << "No object";
    return strm;
}

#define PRINT_ME    std::cout << thisptr(THIS_PTR) << std::endl;

struct TestStruct
{
    void Function1()
    {
        PRINT_ME;
    }
    TRACK_THIS_OBJECT
};

int main()
{
    TestStruct o1, o2;
    PRINT_ME;       // No object
    o1.Function1(); // Object: (identifier1)
    o2.Function1(); // Object: (identifier2)
    return 0;
}



3 Answers

The simplest trick would be to declare a dummy class at the top level with a pointer instance called "this".

Your macro would then always see a "this", and could decide not to print because this==&dummy.

The downside is that "this" is a reserved word, and probably compiler warnings, errors, and standards writers will not be your friend. You may get away with it because the compilers try to maintain backward support, and non-class methods don't /need/ to reserve "this".

Another option would be a global symbol with a complex name, and a macro that declares the same complex name as a class member that you put in all the classes you want to trace. Your macro would then see the class member instance when compiled in a member function of a class where you have declared your extra symbol, but see the global when compiled in a non-member function.

The cheapest symbol to declare would be an enum, as they are just constants, but then how do you distinguish between the global and class variants when there are many class variants? You could certainly use the typeid name as a synonym for the containing class.

Those are my ideas for what they are worth.

The next cheapest is probably an inline member function, perhaps also an inline static member function, and a global function, which could return your unique identifier (their this pointer, and perhaps also their typeid object).

The much simpler solution is different named versions of the logging macro that you use in the different contexts. The compiler will "help" you when you use the wrong one.

I change your code to add empty member (C++20) to have expected output:

_detect_class_ m; // To avoid error in THIS_PTR.

#define TRACK_THIS_OBJECT using _class_sentinel_ = _detect_class_; \
                          [[no_unique_address]]_detect_class_ m;

#define THIS_PTR std::is_same<_detect_class_, _class_sentinel_>::value ? &m : nullptr

Demo

Maybe this is too simple for advanced C++ gurus

// this is in header pretty_print.h
#include <iostream>
inline void pretty_print() {}

#define PRINTABLE                                                              \
  void pretty_print() { std::cout << this << std::endl; }
#define PRINT_ME pretty_print()
// end of header

// #include "pretty_print.h"
void moo() { PRINT_ME; }

struct Foo {
  PRINTABLE

  void foo() { PRINT_ME; }
};

int main() {
  PRINT_ME;
  moo();
  Foo().foo();
}

Related