0

Backstory (You can skip)

In trying to form the question as best I could, I was informed that the second set of parentheses for a #define macro is not a return like I thought it was, but actually a cast. To that, as far as I know; the only way to capture the expression text is by using the #define macro. The tricky part, perhaps impossible, is to capture the explicit type's text, but what do I know?

Also; the reason I specified this to Qt and not just C++/gcc, is that I figure that a solution might involve a class like QVariant which adds some polymorphism, however this is not ideal as it is difficult to distinguish between some various basic types, like int vs bool.

What I am hoping to achieve

Basically, I want a wrapper, I am guessing a macro, which will log the nested expression text, its value, and return its exact nested's type (no implicit conversions).

For example:

/* Basic Definitions */
// log() could be overloaded or use different namespaces if need be
// and have preproccessor directives choose the appropriate one,
// although I dont suspect that is possible.
QVariant log(QString expression, QVariant value, QString type); 

// TYPE is placeholder code. Normally I specify a real type like "bool"
// I am hoping TYPE can represent the captured type text
// #EXPR returns the text of the expression, in case you didnt know
#define LOG(EXPR)(TYPE)(log(#EXPR, EXPR, TYPE));

In this sample code:

/* Sample Code */
QString path;
if (LOG(path.isEmpty())) {
    path = LOG(QDir::currentPath());
}

The wrapper LOG should log the following:

Expression: "path.isEmpty()" Value: "True" Type: "bool"
Expression: "QDir::currentPath()" Value: "/home/akiva/Programming/" Type: "QString"

Id like to see if I could achieve this without having to resort to multiple macro namespaces, like this:

/* Bad solution because I dont want to have to specify LOG_bool & LOG_QString */
QString path;
if (LOG_bool(path.isEmpty())) {
    path = LOG_QString(QDir::currentPath());
}

and I figure one possible solution would reveal itself if I could capture the text of the type, but I am open to all solutions, be it Macros, Templates, Functions or what have you.

My minimum requirements are:

  • Needs to capture the expression text. (I know how to do that with macros)
  • Only require one wrapper namespace, because I dont want to accidentally use the wrong wrapper corrupting my logs or which may cause an implicit conversion, thus potentially changing the intended behaviour of the wrapped expression.
  • The wrapper must return/cast the exact type of the nested expression, or in other words, not affect the exact intended behaviour of the nested expression.

Being able to actually log the type, like Type: "bool" & Type: "QString" would be nice, but is not required.

I hope this all makes sense. Thanks.

Community
  • 1
  • 1
Anon
  • 2,267
  • 3
  • 34
  • 51
  • 1
    Are you trying to ask for [`typeid(EXPR).name()`](http://en.cppreference.com/w/cpp/language/typeid)? It's implementation-defined and could be mangled. Otherwise, you need some canonical transformation from the `std::type_info` object yielded by `typeid(EXPR)` to a pretty string. – Useless Mar 16 '18 at 15:24
  • 1
    Or, specifically for Qt, just call `QVariant::typeName()` on the value argument ... – Useless Mar 16 '18 at 15:27
  • @Useless I will take what I can get. The type name is nice to have for logging, but ultimately the goal is a wrapper which will wrap expressions, log them, but not prevent the expression from serving their purpose. for example `if (path.isEmpty()){}` should still function the same if I do `if (LOG(path.isEmpty())){}` as should `QString path = LOG(QDir::currentPath());` – Anon Mar 16 '18 at 15:56

1 Answers1

1

The basic approach is not to use a QVariant, but to have a template that returns the value of the expression, of the same type:

#define LOG(EXPR) (log(#EXPR, EXPR))

void log(const std::string &); // logging function
std::string demangle(const char*); // demangler - see below
template <typename T> T log(const char * expr, T &&value) {
  auto const &type = std::typeid(value);
  std::ostringstream os;
  os << "expression \" << expr << "\" = (" << value << ") type: "
      << demangle(type.name());
  log(os.str());
  return std::forward(value);
}

The demangler can be adapted from this answer:

#ifdef __GNUG__
#include <cxxabi.h>
#include <memory>
std::string demangle(const char *name) {
  int status;
  std::unique_ptr<char, void(*)(void*)> res {
    abi::__cxa_demangle(name, NULL, NULL, &status),
    std::free
  };
  return {(status==0) ? res.get() : name};
}
#else
std::string demangle(const char *name) {
  return {name};
}
#endif

If you want to use Qt types, you'd use QString instead of std::string:

void log(const QString &); // logging function
QString demangle(QLatin1String); // demangler - see below
template <typename T> T log(QLatin1String expr, T &&value) {
  auto const type = demangle(std::typeid(value).name());
  log(QStringLiteral("expression \"%1\" = (%2) type: %3")
      .arg(expr).arg(value).arg(type));
  return std::forward(value);
}

#ifdef __GNUG__
#include <cxxabi.h>
#include <memory>
QString demangle(QLatin1String name) {
  int status;
  std::unique_ptr<char, void(*)(void*)> res {
    abi::__cxa_demangle(name.latin1(), NULL, NULL, &status),
    std::free
  };
  return {(status==0) ? QLatin1String(res.get()) : name};
}
#else
QString demangle(QLatin1String name) { return {name}; }
#endif
Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
  • I worked around with it. I had to replace the `QLatin1Strings` with `QStrings` for it to compile. Not sure if that is a big deal... and I could not call `std::typeid`, and instead had to just do `typeid`, and `return std::forward(value);` became `return std::forward(value);` -- It seems to be working swimmingly. Many thanks. – Anon Mar 17 '18 at 02:45