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.
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...