How to tell if you’re in a method in C++

An issue came up at work that we wanted some generic logging code to be able to report the object it was being called from. The tricky part is that we wanted the same logging macro to work in non-method context as well. Any use of this is impossible because it will cause a compile error in the latter case.

In short, both the following instances ought to work, but the one in the class method should automatically know to print some extra gubbins about the object:

struct Something {
   void someMethod() {
      LOG("Some logging message");
   }
};
 
void someFunction() {
   LOG("Some other logging message");
}

I thought this was an interesting case, not so much for the eventual solution as for the thought process involved. One of the big problems with C++ is that although you have no shortage of powerful tools that enable metaprogramming, they were never designed to provide a coherent system. It takes a bit of experience even to know what’s possible, let alone how to go about finding a way to do something. With most metaprogramming the pattern is to find one language feature that yields the information you need (perhaps a type trait, a typedef, a function return value or whatever) and abuse some language feature (sizeof, SFINAE, template argument deduction etc.) to allow the original feature to indirectly feed through to generate a usable compile-time value such as an enum or typdef.

Obviously macros are going to be involved here: no other tool in C++ allows for code reuse and gives the repeated code access to the scope in which it is invoked. But macros are purely a lexical concept, so we’ll have to include something else that operates at a syntactic level to discriminate between the cases.

So what’s different in state between member and non-member functions in C++? There’s the this pointer, for a start. Also, a different namespace scope is available and potentially different accessibility to private and protected class members. Other than that, there’s not much else in the ISO standard (compiler extensions like __PRETTY_FUNCTION__ might help, though).

Using the this keyword is the most direct route to the information we need, since it pretty much is the information we need. Unfortunately, the definition of this is such that even mentioning it when outside of object context is an instant compile error. Since we can’t so much as mention it in our macro definition, we’re going to have to be a bit craftier.

The problem is that using this outside of a method is a syntax error, which will kill our compile immediately. Except there’s precisely one case where the compiler can recover from a syntax error and take a different path: instantiating a template. If the compiler attempts a template instantiation and gets a compile error, it may discard that template specialisation and try another possibility. This is surprisingly useful and even gets its own name, the rather snappy Substitution Failure Is Not An Error.

Unfortunately SFINAE isn’t going to help us here, because the area of expanded code where an error is allowable is within a different scope than the place we’re trying to log from: it’s either always in a free function scope, or always in a method scope (depending on whether we use a template function or a template class for SFINAE), but it will never depend on the calling context.

So we’re back to the drawing board, except that we know that we can’t mention this in our macro. This leaves us with two possible ways to discriminate the cases: class member accessibility and function overloads in scope. As an example of the former, consider the following:

#include <iostream>
 
using namespace std;
 
class Something {
public:
	static void func(...) {
		cout << "Ordinary function, anyone can see this" << endl;
	}
 
	void method() {
		Something::func(42);
	}
 
private:
	static void func(int n) {
		cout << "Private function, you need to be privileged" << endl;
	}
};
 
int main(int argc, char ** argv) {
	Something::func(42);
 
	Something something;
	something.method();
}

This is how you might like it to work: the call in main() gets the unprivileged overload, and the call in Something::method() gets the more specific overload that it can only see because it’s in the context of the class. This, by the way, works by a quirk of C++ that varargs functions are always used as a last resort and only if an overload that matches better is not available. Let’s ignore for the moment the fact that this only detects if we’re in a special sort of class (namely the one that defines func(), or a friend of it) and not the general distinction between class and non-class.

Unfortunately, we can’t use accessibility in this way because function overload resolution happens before accessibility is checked. Rather than getting a different result from the overload resolution in each case, you get the same result from overload resolution in each case and a compiler error if the preferred form is not accessible. You may be thinking that SFINAE could save us here, but in fact we have the same problem as before: the call will take place outside of the scope we’re trying to detect.

So the only thing we have left that distinguishes between free function context and method context is the different set of functions in scope:

#define LOG( x ) cout << (getThis() ? "In method" : "In function") 
   << ": " << x << endl;
 
void * getThis() {
   return NULL;
}
 
struct Fish {
   void * getThis() {
      return this;
   }
 
   void method() {
      LOG("Log line");
   }
};
 
void function() {
   LOG("Log line");
}

This is promising, actually. In fact, it’s pretty close to what we want except for the fact that we have to define this log method in every class that we want to be able to log from. This isn’t as bad as it first seems, since the only reason to get hold of the this pointer in practice is to call methods on it, and we can only do that if all the classes implement a common interface anyway.

In practice, the getThis() construction is a little weak. It only returns a void *, and in order to call any methods we’ll have to cast it back to the proper type, and we’re back to the problem of not knowing the type of the class we’re in (or even if we have a class type). Rather than trying to implement reusable code in a macro definition, any code that needs to know about types will have to be in a method that’s implemented for every class we care about, and corresponding stubs at global scope. This is pretty horrible, and will probably render this technique useless.

So in the end we don’t really have a solution, but at least we can be reasonably confident that we’re not missing anything.

Much of the above code doutless contains errors, as I haven’t tested a lot of it. Feel free to write in with corrections.

4 thoughts on “How to tell if you’re in a method in C++

  1. Jack V

    Stupid question #2: what sort of gubbins are you talking about? Just the name of the class, or a pointer to it, or a detailed dump of the contents?

    Reply
  2. Jack V.

    Stupid question #3: You say multiple stubs, but don’t you need a global logging function anyway to be called from global functions, which can be declared the same place as the macro or the joint base interface class, so _is_ there redundency?

    Reply
  3. admin Post author

    Question 1: Yes, but the question is really about the difference between code that’s closely associate with a particular instance (has access to private members) and code that’s not closely associated with any particular instance. Whether it’s in a class or not is only really a heuristic.

    Question 2: In actual fact most of the classes in our code follow a common protocol, so you could reasonably ask for quite a few members you could expect to be there. I won’t go into proprietary details, but obviously each class has a name, and you can imagine that certain classes would have a property where every instance would have a globally-unique canonical human-readable identifier, which would be nice to have in the log.

    Question 3: I’m not sure I follow. I was anticipating a global logging function (probably an ostream actually) that can log whatever’s passed to it, and class-local adapters that would package up instance-specific information and pass it to the generic logging code. The packing-up code would have to be duplicated to some extent.

    I’m not sure the duplication’s as bad as I thought – I think you can play games with covariant return types and the Curiously Recurring Template Pattern that will avoid the need for downcasting. I’m afraid the latter half of this post was rather rushed and lost its coherence, and I’m sufficiently tired that I don’t feel like going back and fixing things. I might write another post about CRTP at some point though.

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *