solution: an in-language way of mapping class names --> strings

S

stephan beal

Good afternoon, C++ers,

This weekend i came across a fairly project-neutral trick which can be used
to map C++ class names to their human-readable equivalents (a-la QObject's
className() method), without having to add methods to a client interface
and without relying on (e.g.) typeid(T).name().

i understand that this may not be strictly a language-related post, but i
think the solution is "template-enough" to be considered topical here.

Sample usage:
#include "class_name.h"
....
CLASS_NAME(MyClass);
....
cout << class_name<MyClass>() << endl;
or
cout << class_name<MyClass>::name() << endl;

the code:

#ifndef CLASS_NAME_H_INCLUDED
#define CLASS_NAME_H_INCLUDED 1
#include <cassert>

namespace { // anonymous ns, important for linking reasons.

/**
A utility class to provide human-usable class names, available at
runtime.

It MUST be specialized to work.

Call the CLASS_NAME(Type) or
CLASS_NAME_ALIAS(Type,AliasForType) macros from somewhere
in the global namespace (in a header, not an impl file) to
register a class_name<Type> specialization for Type. It may
only be called one time per Type per compilation unit, or
you will get specialization collisions at compile time.

Ideally, CLASS_NAME() is called from a class' header file.

Caveats:

- template types with commas in the names will break the
macro and...

- typedef'd names will get their typedef'd name, not their
real name. Maybe a feature, maybe not.
*/
template <class T> struct class_name
{
// static const char * classname = "...";
// ^^^^ we can't initalized a non-integral type this way,
thus the bloat show below...

class_name(){}
~class_name(){}
static const char * name()
{
assert( 0 /* this class_name<> is unspecialized!
*/ );
return "error::class_name<unspecialized>";
}
operator const char * () const
{
return name();
}
};

} // namespace

#define CLASS_NAME(Type) CLASS_NAME_ALIAS(Type,Type)
#define CLASS_NAME_ALIAS(Type,Alias) \
namespace {\
template <> struct class_name<Type> {\
class_name(){}; ~class_name(){}; \
static const char * name() {return # Alias; }\
operator const char * () const { return name(); }\
};}
#endif // CLASS_NAME_H_INCLUDED

i now use this as the basis for a classloader.

Enjoy...

--
----- stephan beal
Registered Linux User #71917 http://counter.li.org
I speak for myself, not my employer. Contents may
be hot. Slippery when wet. Reading disclaimers makes
you go blind. Writing them is worse. You have been Warned.
 
T

tom_usenet

Good afternoon, C++ers,

This weekend i came across a fairly project-neutral trick which can be used
to map C++ class names to their human-readable equivalents (a-la QObject's
className() method), without having to add methods to a client interface
and without relying on (e.g.) typeid(T).name().

i understand that this may not be strictly a language-related post, but i
think the solution is "template-enough" to be considered topical here.

Standard C++ idioms are certainly on topic.
#ifndef CLASS_NAME_H_INCLUDED
#define CLASS_NAME_H_INCLUDED 1
#include <cassert>

namespace { // anonymous ns, important for linking reasons.

Why? All your methods are inline so you're better off putting the
class in the global namespace, IMHO, so that instantiations might be
shared. In practice, everything will be inlined so it probably doesn't
make much difference either way.

/**
A utility class to provide human-usable class names, available at
runtime.

It MUST be specialized to work.

Call the CLASS_NAME(Type) or
CLASS_NAME_ALIAS(Type,AliasForType) macros from somewhere
in the global namespace (in a header, not an impl file) to
register a class_name<Type> specialization for Type. It may
only be called one time per Type per compilation unit, or
you will get specialization collisions at compile time.

Ideally, CLASS_NAME() is called from a class' header file.

Caveats:

- template types with commas in the names will break the
macro and...

- typedef'd names will get their typedef'd name, not their
real name. Maybe a feature, maybe not.
*/
template <class T> struct class_name
{
// static const char * classname = "...";
// ^^^^ we can't initalized a non-integral type this way,
thus the bloat show below...

class_name(){}
~class_name(){}
static const char * name()
{
assert( 0 /* this class_name<> is unspecialized!
*/ );
return "error::class_name<unspecialized>";
}
operator const char * () const
{
return name();
}
};

How about:

template <class T> struct class_name; //no definition

That way you'll get compile time errors when trying to use it with
unregistered classes.
} // namespace

#define CLASS_NAME(Type) CLASS_NAME_ALIAS(Type,Type)
#define CLASS_NAME_ALIAS(Type,Alias) \
namespace {\
template <> struct class_name<Type> {\
class_name(){}; ~class_name(){}; \

Why the empty constructor and destructor? The compiler will generate
them for you.
static const char * name() {return # Alias; }\
operator const char * () const { return name(); }\
};}
#endif // CLASS_NAME_H_INCLUDED

i now use this as the basis for a classloader.

It's a good idea.

Tom
 
S

stephan beal

tom_usenet said:
Why? All your methods are inline so you're better off putting the
class in the global namespace, IMHO, so that instantiations might be
shared. In practice, everything will be inlined so it probably doesn't
make much difference either way.

This is a valid point. First, let me agree that it "probably" doesn't make a
difference. i ended up using an anon namespace because:

a) i was using no global-space code in the tree which this evolved from.
b) because i had lots of dual-definition collisions when puting class_name
into those namespaces (i don't know why,though).

That said, the ODR violations were very possibly caused by me doing
something stupid as far as include files went. Since the anon ns seems to
work okay i stick with that. (i'm new to template specialization and want
to remove as many variables (no pun intended) as possible while i learn the
ropes.)

Part of the complication is that many of my CLASS_NAME() usages are in
classes which are compiled into static libraries and/or DLLs (or both). To
avoid template-instantiation-related problems i keep the CLASS_NAME() calls
in the class headers, NOT in their impl files (which would be okay if all
the participating classes are in the same lib/app).

Again, though, i'll admit that i'm fairly new to this level of linking
complication, and i may be using a naive approach. "It seems to work," but
there may be a better way to handle that. i'll give it a try using the
global NS, as you suggest. i believe that my anon-ns implementation has an
evil side-effect in my classloader, anyway, causing multiple-registations
for classes where CLASS_NAME() is called from several impl files (that's
another matter entirely, however, and one which i'll cover in a separate
post).
It's a good idea.

Thanks :)
i appreciate the feedback!

--
----- stephan beal
Registered Linux User #71917 http://counter.li.org
I speak for myself, not my employer. Contents may
be hot. Slippery when wet. Reading disclaimers makes
you go blind. Writing them is worse. You have been Warned.
 
S

stephan beal

Sorry, i completely missed these notes in my first response:
How about:

template <class T> struct class_name; //no definition

That way you'll get compile time errors when trying to use it with
unregistered classes.

That's a great idea, thanks :).
Why the empty constructor and destructor? The compiler will generate
them for you.

Habit :/


--
----- stephan beal
Registered Linux User #71917 http://counter.li.org
I speak for myself, not my employer. Contents may
be hot. Slippery when wet. Reading disclaimers makes
you go blind. Writing them is worse. You have been Warned.
 
S

stephan beal

tom_usenet said:
Why? All your methods are inline so you're better off putting the
class in the global namespace, IMHO, so that instantiations might be
shared. In practice, everything will be inlined so it probably doesn't
make much difference either way.

i experimented with this a bit last night, and the problem is rather
complex, but boils down to requiring an anonymous namespace.

It's like this:

i've got classes which are used in these different contexts:

a) in a static lib
b) in a DLL with a group of other classes
c) alone in a DLL
d) statically linked directly by client apps

The anon ns is the only way i can reliably link all of these cases without
getting multiple definition collisions at link time. Not only that, but the
CLASS_NAME stuff must be called in the class HEADERs, not the impl files,
or else some of the above contexts will work and some will not. e.g., let's
assume we do this in Foo.cpp:

CLASS_NAME(Foo);

the class_name<Foo> specialization gets compiled into Foo.o. Now we link
Foo.o into mylib.a - the specialization doesn't survive the trip, requiring
the client to link against Foo.o. Calling the macro from the header gets
around this in all known cases at the slight cost of a couple extra
instantiations.

--
----- stephan beal
Registered Linux User #71917 http://counter.li.org
I speak for myself, not my employer. Contents may
be hot. Slippery when wet. Reading disclaimers makes
you go blind. Writing them is worse. You have been Warned.
 
T

tom_usenet

i experimented with this a bit last night, and the problem is rather
complex, but boils down to requiring an anonymous namespace.

It's like this:

i've got classes which are used in these different contexts:

a) in a static lib
b) in a DLL with a group of other classes
c) alone in a DLL
d) statically linked directly by client apps

Ahh, I can see that might cause problems.
The anon ns is the only way i can reliably link all of these cases without
getting multiple definition collisions at link time. Not only that, but the
CLASS_NAME stuff must be called in the class HEADERs

Yes, of course - specializations have to have been declared in order
to legally use them, although some compilers let you get away with
this.

Tom
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,982
Messages
2,570,190
Members
46,740
Latest member
AdolphBig6

Latest Threads

Top