0

When being a) in a c++ free function or b) in a class' member function:
How can i pass a) nullptr respecively b) the this to a generic macro that should work for both?
I know, we shouldn't use macros - but i have legacy code. Maybe later the macro can be replaced. But i think the question remains the same: How to detect if a) in c++ free function or b) in a class' member function and as a consequence use a) nullptr or b) this ptr for processing?

#include <iostream>

#define MYMACRO                                                                                                         \
{                                                                                                                       \
    if (1) { /* what to use here? */                                                                                    \
        std::cout << "MYMACRO used in a member function of class this ptr = " << "" /* print this here */ << std::endl; \
    }                                                                                                                   \
    else {                                                                                                              \
        std::cout << "MYMACRO used in a free function" << std::endl;                                                    \
    }                                                                                                                   \
}


struct MyStruct
{
    MyStruct() = default;
    ~MyStruct() = default;
    void SomeMemberFunction(void)
    {
        MYMACRO;
    }
};

void SomeFreeFunction(void)
{
    MYMACRO;
}

int main(int, char**)
{
    MyStruct myStruct;
    myStruct.SomeMemberFunction();
    SomeFreeFunction();
    return 0;
}

i.e. nullptr or this ptr should be detected within the MYMACRO.

Addendum #1
Some comments below asked for purpose - in concrete from @Nicol Bolas.
Imagine MYMACRO has some functionality already but shall be extended to additionally provide a new logging function (optionally). When MYMACRO is used for a member function the class name and this ptr value will be logged for tracing and correlation. Just as an example. Then MYMACRO for this needs to know if used in a context of a class member function or not (i.e. if some this ptr is available or not).

Talking in general: it's about "reflection"

Frank Bergemann
  • 325
  • 2
  • 14
  • there is no `this` for free function – Swift - Friday Pie Jan 11 '23 at 08:06
  • 5
    Please provide examples of how this gets called. Also please consider whether this is a https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem . – Yunnosch Jan 11 '23 at 08:11
  • https://stackoverflow.com/questions/23444371/how-to-write-safe-and-user-friendly-c-c-define-macros – Swift - Friday Pie Jan 11 '23 at 08:12
  • The better question shall be why you need `this` and what do you want to do with `this`. Since macro is more of string substitution, how does macro aware of a `class`? – Louis Go Jan 11 '23 at 08:16
  • There are no standard way for macro to figure in what context it is expanded. The code in it can analyse contents of `__func__` and decide something, but the value of `__func__` is implementation-specific. – Öö Tiib Jan 11 '23 at 08:16
  • 1
    You failed to tell us WHAT you wanted to achieve, because I am quite sure your HOW (macros) isn't the best approach. So can you tell us what you are trying to do? – Pepijn Kramer Jan 11 '23 at 08:20
  • Don't use macros, in C++ function templates of function overloads. You can specialize those to detect both situations independently and you can provide the correct implementation. – Pepijn Kramer Jan 11 '23 at 08:21
  • Can you provide an example of what `MYMACRO` will do with `this` if called in a member function, and what it will do if not? And, if there is a functional difference between the two cases, please explain why they need to be handled by the same macro. – Peter Jan 11 '23 at 08:32
  • Why is it essential to you to use the same macro in both places? – molbdnilo Jan 11 '23 at 08:35
  • Can you *change* the macro (especially, its "signature")? As it is, the macro simply does not have the information it needs (no pointer is passed). If you change it to `MYMACRO(p, x)` and call it with `MYMACRO(nullptr, whatever)` from freestanding functions and `MYMACRO(this, whatever)` from member functions it seems simple. For simplicity and clarity, you could also simply define two different (for example, one additional) macros. – Peter - Reinstate Monica Jan 11 '23 at 08:44
  • i provided a complete example code above - to make it more clear – Frank Bergemann Jan 11 '23 at 09:16
  • "Why is it essential to you to use the same macro in both places?" I don't want to change the user code - only the macro. The macro itself shall find out, if it is used in a free function or in a member function. The 'this' pointer then *optionally* will be used in the macro (in macro is used in member function) – Frank Bergemann Jan 11 '23 at 09:17
  • 1
    The comments are full of questions... why we do not simply close with "needs details ...". For me it is totally unclear what should be achieved. A macro should never be a solution, but as long the task is totally unclear, we need an update tothe question. Please close! – Klaus Jan 11 '23 at 09:47
  • @FrankBergemann: "*I don't want to change the user code - only the macro.*" Well, how did it work before, and what are you trying to make it do now that it didn't before? – Nicol Bolas Jan 11 '23 at 20:46

4 Answers4

0

For free-standing function there is no this, so that part of question doesn't make sense. If you're meaning how to check if local variable x is valid, that's pointless. It's always valid and within its methods this would not be nullptr unless your code boke something. horribly (e.g. somehow broke stack from other thread).

Macro definitions simply replace code, so if you can write

bool SomeClass::MemberFunc(void)
{ 
  if(!this) return;
  doSomething();
} 

you can write

#define CHECK_THIS  if(!this) return
bool SomeClass::MemberFunc(void)
{ 
  CHECK_THIS;
  doSomething();
} 

Macro unwrapping happens before compilation, preprocessor doesn't analyze your code, so you cannot change macro content contextually. I.e. it must happen in code.

One "dirty" way is to define some global function which would have local counterpart in every object. In that case any method of object that doesn't contain that definition, would behave as there is no this.

It can be done by inheritance, like paddy had shown in his answer, or another macro in class body:

//  global MYMACRO for free fuctions.
void MYMACRO ()  {};

#define DECLARE_MYMACRO \
 void MYMACRO ()  { \
    if(!this) doSomething(); \
 } 

class MyClass {
   DECLARE_MYMACRO
public:
   void memberFunc(void);
};

Is as a rule, check if this is nullptr is a paranoid AND useless effort. You may try that for debugging purposes only, but you cannot use that to create falsely "rugged" code. Thing is a call to member function with null pointer to class-type instance is Undefined Behaviour. The compiler is free to assume that Undefined Behavior didn't happen. So, on practice, a compiler may just throw that if out, because this cannot be nullptr within defined behaviour borders. That's not hypothetical, popular compilers do that when optimization is on.

The check if the address of object is nullptr must happen before the very first use of its member in particular scope, i.e. outside of member. After first use or call gloves are off, the check makes no sense.

Swift - Friday Pie
  • 12,777
  • 2
  • 19
  • 42
0

If you don't mind inheriting an interface class for any class that needs to invoke your macro, you can take the following approach:

template <class T>
void DoSomething(T* ptr, SomeVar x)
{
    std::cout << "Doing something with ptr=" << ptr << "\n";
}

void MYMACRO(SomeVar x)
{
    DoSomething<void>(nullptr, x);
}

Note that I will use "MYMACRO" simply as a reference to the part of your program that does the work. It is obviously NOT a macro! Instead, it's just a function that will invoke DoSomething with a NULL pointer.

In order to overload this behavior in classes, you must define an interface:

class SomethingDoer
{
protected:
    void MYMACRO(SomeVar x)
    {
        DoSomething(this, x);
    }
};

And then, any class that needs to invoke it should inherit this interface. When a method of that class calls MYMACRO, it will use SomethingDoer::MYMACRO instead of the other function.

class SomeClass : public SomethingDoer
{
public:
    bool MemberFunc();
};

bool SomeClass::MemberFunc()
{ 
    SomeVar x;
    MYMACRO(x);
    return false;
}

If your macro does other stuff as well that is more convenient to use macros for, that's fine. You can still implement as a macro, but have the "this"-related stuff use the mechanism I described.

And if you really need the type of this to be correct, a small adjustment is:

template <class T>
class SomethingDoer
{
protected:
    void MYMACRO(SomeVar x)
    {
        DoSomething((T*)this, x);
    }
};

class SomeClass : public SomethingDoer<SomeClass>
{
public:
    bool MemberFunc();
};
paddy
  • 60,864
  • 6
  • 61
  • 103
0

this does half the job for c++17
This approach does not work, because __PRETTY_FUNCTION__ also has ':' for a free function that is nested in a namespace. (see comment from Nicol Bolas below - thanks Nicol!)

#include <string_view>
#include <iostream>

constexpr bool ContainsColon(std::string_view str)
{
    return str.find(':') != str.npos;
}


#define MYMACRO                                                                                             \
{                                                                                                           \
    if constexpr(ContainsColon(__PRETTY_FUNCTION__)) {                                                      \
        constexpr void *ptr = nullptr; /* still todo */                                                     \
        std::cout << "MYMACRO used in a member function of class this ptr = " << (long)ptr << std::endl;    \
    }                                                                                                       \
    else {                                                                                                  \
        std::cout << "MYMACRO used in a free function" << std::endl;                                        \
    }                                                                                                       \
}


struct MyStruct
{
    MyStruct() = default;
    ~MyStruct() = default;
    void SomeMemberFunction(void)
    {
        MYMACRO;
    }
};

void SomeFreeFunction(void)
{
    MYMACRO;
}

namespace TestSpace {

void SomeOtherFreeFunction(void)
{
    MYMACRO;
}

} // namespace TestSpace

int main(int, char**)
{
    MyStruct myStruct;
    myStruct.SomeMemberFunction();
    SomeFreeFunction();
    // ERROR! - this also reports to be called from a member function :-(
    TestSpace::SomeOtherFreeFunction();
    return 0;
}

output:

MYMACRO used in a member function of class this ptr = 0
MYMACRO used in a free function
MYMACRO used in a member function of class this ptr = 0 # WRONG HERE!
Frank Bergemann
  • 325
  • 2
  • 14
0

I think you best bet is doing something like this:

#include <type_traits>
#include <iostream>

#define DECLARE_GETTHIS() auto get_this() {return this;}
#define MYMACRO()  log_this(get_this())

void *get_this() {return nullptr;}

template<typename T>
void log_this(T *_this) {
    if constexpr (std::is_void_v<T>) {
        std::cout << "We are in freestanding function" << std::endl;
    } else {
        std::cout << "We are in class function, this: " << _this << std::endl;
    }
}

struct S {
    DECLARE_GETTHIS();
    void func() {
        MYMACRO();
    }
};

int main() {
    MYMACRO();
    S s;
    s.func();
}

Of course for this to work you will need to insert DECLARE_GETTHIS(); in ALL base classes. But I don't see any other ways without some modifications in classes.

Declaration of global get_this() can be placed in same header where MYMACRO() is defined with inline specifier.

Also, constexpr can be dropped in if constexpr if you need this to work for older versions.

sklott
  • 2,634
  • 6
  • 17