Reputation: 5945
I am trying to write a logging routine that (almost) automatically traces the entry and exit to a method. Something like:
int rc=0;
LOG_ENTRY("MyFunction()", rc);
LOG_ENTRY is a macro that defines a local object. When the object is created it will call the logger with "Entry MyFunction()" and when the object is destroyed at the end of the traced method, the destructor will issue "Exit MyFunction() returned 0" where 0 is the value of rc on exit.
Now I have most of this working using c++ except that the constructor for the class for the logging object has to be defined for every type of return value:
class IM_EX_CTIBASE LogEntry
{
Log *logger;
const char* func;
void *rc;
public:
LogEntry(Log *logger, const char* func, int *rc)
{
this->logger=logger;
this->func=func;
this->rc=rc;
logger->log("Entry " << func);
}
~LogEntry()
{
logger->log("Exit " << func << *rc);
}
}
(please forgive any typos, this is a simplified version of the actual code.)
Now I was hoping to rewrite this as a template, where the type rc could be externally defined. I was thinking that auto would be an excellent possibility for defining rc on the constructor, but auto does not work there.
Since the compiler already knows what the type is, why do I need to define it here. the log() (which accepts a stream) should already know how to use rc.
I've looked at many alternatives, but nothing seems to be what I want. Someone suggested "do it like make_pair does", but after digging into that it appears to create a function template which I don't think you can use in a constructor.
Anyone have any ideas?
Upvotes: 0
Views: 195
Reputation: 367
I was going to paste you a personal logging system I used in a project but it's quite badly tied into the system and Qt. Igor's answer looks good, I would add that you can use the (I think possibly g++-specific) __FUNC__ (or __PRETTY_FUNCTION__) macros to avoid having to type the function name each time. Another possibility is to define a RETURN macro that uses SFINAE to detect if the return type can be streamed to cout/cerr, and if so, does this and then returns the value form the function proper. That can be done like so:
#ifdef SUPPORTS_CPP_11
template<class T> struct ValuetoString{ //SFINAE class to check if a value type can be converted implicitly to
// a printable string, and if so, print() returns a string for a value of type T
typedef char Yes;
typedef struct{ char a[2]; } OStream;
template<typename J> static OStream Test( //For types that output to std::ostream
typename UnReference<decltype(std::declval<std::ostream&>() << std::declval<J>())>::Type *param);
template<typename J> static UnPrintable Test(...); //Always prever above if it be well-formed.
template<bool ostream, class Dummy> struct print;
template<class Dummy> struct print<true, Dummy>{
static inline std::string ret(T Value){
std::ostringstream a; a << Value; return a.str(); }
};
template<class Dummy> struct print<false, Dummy>{
static inline std::string ret(const T &Value) {
return std::string(BasicTypeName(T) + " is an unprintable type"); }
};
static inline std::string Print(const T &Value){
return print<(sizeof(Test<T>(NULL, NULL)) == sizeof(OStream)), int>::ret(Value); }
};
template<typename T> inline std::string FriendlyInspect(T val, const char *name){
return std::string(FriendlyTypeName(val, name) + " with value " + ValuetoString<T>::print(val) );
}
template<typename T> inline std::string FriendlyInspect(T* val, const char *name){
return std::string(FriendlyTypeName(val, name) + " with value " + ValuetoString<T>::print(val) );
}
#else
template<typename T> struct ValuetoString{
static std::string Print(T Value) {return std::string() += FriendlyTypeName<T>(Value,"") += " is an unprintable type";}
};
#endif //SUPPORTS_CPP11
If you call FriendlyInspect(9) for example, it will output the typename, 'int', and the value '9', but if you call FriendlyInspect(MyObjectThatCantBeStreamed), it won't generate a compile-time error but will instead just print the typename. I modified it from an original, I don't know if Dummy is still needed but it's doing no harm. In this case FriendlyTypeName uses a g++ function whose exact name I can't remember to demangle the type_info::name() on g++ or on other platforms just leaves it alone, and UnReference just removes the reference from a type.
Upvotes: 0
Reputation: 52471
Something along these lines, perhaps:
template <typename T>
class LogEntry {
public:
LogEntry(Log *logger, const char* func, T* rc);
};
#define LOG_ENTRY(name, ret) \
LogEntry<decltype(ret)> log_entry(logger, name, &ret);
Filling in the details of LogEntry
s implementation is left as an exercise for the reader.
Upvotes: 3