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

Discussion in 'C++' started by stephan beal, Oct 15, 2003.

  1. stephan beal

    stephan beal Guest

    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.
     
    stephan beal, Oct 15, 2003
    #1
    1. Advertising

  2. stephan beal

    tom_usenet Guest

    On Wed, 15 Oct 2003 12:18:47 +0200, stephan beal
    <> wrote:

    >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
     
    tom_usenet, Oct 15, 2003
    #2
    1. Advertising

  3. stephan beal

    stephan beal Guest

    tom_usenet wrote:
    > On Wed, 15 Oct 2003 12:18:47 +0200, stephan beal
    > <> wrote:
    >>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.


    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.
     
    stephan beal, Oct 15, 2003
    #3
  4. stephan beal

    stephan beal Guest

    tom_usenet wrote:
    >> }


    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.
     
    stephan beal, Oct 15, 2003
    #4
  5. stephan beal

    stephan beal Guest

    tom_usenet wrote:
    > On Wed, 15 Oct 2003 12:18:47 +0200, stephan beal
    > <> wrote:
    >>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.


    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.
     
    stephan beal, Oct 16, 2003
    #5
  6. stephan beal

    tom_usenet Guest

    On Thu, 16 Oct 2003 12:15:45 +0200, stephan beal
    <> wrote:

    >tom_usenet wrote:
    >> On Wed, 15 Oct 2003 12:18:47 +0200, stephan beal
    >> <> wrote:
    >>>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.

    >
    >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
     
    tom_usenet, Oct 16, 2003
    #6
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Craig
    Replies:
    0
    Views:
    474
    Craig
    Feb 9, 2004
  2. Ares Lagae
    Replies:
    8
    Views:
    465
    Ares Lagae
    Sep 24, 2004
  3. Markus Joschko

    Search for mapping solution

    Markus Joschko, Jul 6, 2003, in forum: Python
    Replies:
    10
    Views:
    514
    Martin Maney
    Jul 10, 2003
  4. Ben

    Strings, Strings and Damned Strings

    Ben, Jun 22, 2006, in forum: C Programming
    Replies:
    14
    Views:
    814
    Malcolm
    Jun 24, 2006
  5. alan
    Replies:
    5
    Views:
    335
Loading...

Share This Page