4

My question is similar to: Can't use macro define class in C++, but is a little more complicated:

class ABC
{
public:
    DECLARATION(ABC)

private:
    void ABCFun1();
    void ABCFun2();
    // ... and so on
}

#define DECLARATION(TYPE)\
    std::string GetClassName()\
    {\
        return std::string(#TYPE);
    }\
    // the macro can goes on to declare more
    // common interfaces, like Initialize(), ...etc.

So, the point is, I can use this macro to generate other classes like DEF, GHI...etc,
where all of them shares the common part of DECLARATION, but also having their own private
parts.

If there isn't the need of GetClassName(), which seems can only be implemented using macro #,
then I can use inheritance to put them together.
Or, if there aren't the private things, I can use template.

So, with these 2 things mixed up, is there any way to avoid the macro?
Thanks!

Community
  • 1
  • 1
Marson Mao
  • 2,935
  • 6
  • 30
  • 45

3 Answers3

2

You can us the CTRP to generate some functions. But remember that names won't be available unless you use macros (that will change when compile-time reflection will be made into the standard, maybe for C++17).

CRTP:

template<typename T>
class Base
{
public:
    void myFunc()
    {
        ((T*)this)->functionA();
        ((T*)this)->functionB();
        // ...
    }
};

class ABC : public Base<ABC>
{
    // ...
};

Edit: BЈовић answer is right, but some comments about RTTI:

  • it is not portable
  • typeid(T).name() is implementation dependant (it can return "" for every type if an implementor wanted to)
  • it is slow.

See this question about RTTI and LLVM, for example (or this one about performance).

To store strings, you should use the preprocessor as for now. Note that it does not work with templated parameters (ie, if you have template<typename T>, then #T will expand to "T", not the actual typename). Then, there is a technique that allows to pass a string to a template parameter (sort-of):

#define NAME( _n_ , _t_ )             \
    struct _t_                        \
    {                                 \
        static const char* asName()   \
        {                             \
            return #_n_;              \
        }                             \
    }

It changes the string to a type. The string in itself is a litteral, so it is put straight into the executable (and is read-only, attempts to modify it will most likely result in a crash).

I use this in the SPARK Particle Engine, for example, where I implemented a reflection module.

Community
  • 1
  • 1
Synxis
  • 9,236
  • 2
  • 42
  • 64
  • Yes, I dont want to use `typeid(T).name()` either! – Marson Mao Feb 27 '14 at 10:39
  • One day, this name will be available using C++ standard reflection (I don't think they will be ready for C++14, so probably C++17). – Synxis Feb 27 '14 at 10:42
  • P.S. has to say thanks at first, then i need some time to read them! – Marson Mao Feb 27 '14 at 10:44
  • Thanks, I've read your answer. CRTP is amazing, which fits my need. Too bad to know the only way of string converting is still the macro, but I guess I can accept the minimal using of `#x` to get the string. – Marson Mao Mar 04 '14 at 11:01
1

As far as I can see from the vague "the macro goes on..." comment, the DECLARATION macro seems to do one or several of these things:

  1. define members are independent of ABC
  2. define members (methods or data members) that depend on the type ABC
  3. define members that use the string "ABC"

tl;dr: #1 is trivial, as you noted in the question. #2 is relatively easy, use CRTP, #3 is not satisfactorily doable without a macro.

update:

In the comments below you mention that you want a guarantee that the string and the class name be "sync-ed". This is definitely impossible without macros in current C++. (see "update" below)

The first is easy to achieve without a macro - just inherit from a ordinary base class that defines the members.

The second can be done relatively easy too, via a CRTP: create a template class that takes the type as parameter and inherit from the template, instantiated with the type itself:

template <class T>
class Base {
public:
  void doSomething(T const&);
};

class ABC : public Base<ABC> {
  // inherited void doSomething(ABC const&);
};

The third one is tricky and not easily solvable without at least a bit of boilerplate.

C++ has no features apart from macros that convert a name (class name, function name, variable name...) into a string representing that name. (such language features are part of a family of features commonly called reflection). An exception is typeid(T).name(), nut the outcome is not standardized, so it may or may not give you the name of the class, something different, readable or unreadable or just an empty string.

So, to (portably and realiably) get a string "ABC" for a class ABC you have to write "ABC" at least once in addition to the class definition. That's not so bad yet, you have to do something similar for #2 as well, as it seems impossible to use the type of ABC without explicitly mentioning ABC again.

Update: Since you have to explicitly type both the string and the class name, there can be no guarantee that both are always the same, except with a macro. Even the macro you have in the question is prone to changes/typos:

class ABC {
  DECLARATION(ACB);
};

This will give a wrong class name if the macro only provides the stringified version of it's argument without doing something with the type (in that case the compiler would tell you that ACB is no type). If you want the guarantee, use one macro that does everything, including the class definition:

#define CLASS_DEF(x)           \
class x##_base {               \
  std::string GetClassName() { \
    return std::string(#x);    \
  }                            \
};                             \
class x : public x##_base

and then later:

CLASS_DEF(ABC)
{
private:
    void ABCFun1();
    void ABCFun2();
    // ... and so on
}

Putting it in a base class makes it possible to write the macro and then just a normal class body.

So what can we do for #3 without a macro, if we don't need the sync-guarantee?

  • providing the string as a template parameter to some base class would be the nicest way but is, in short, not possible. This article covers strings as template parameters and has to fall back to macros in the end. The closest we can get is some form of the boost::mpl::string<'Hell','o Wo','rld!'> mentioned in the article. The length would be limited by some arbitrary definition, if C++11 variadic templates are not available, and the definition of the base class (or boost::mpl::string) would probably use a good deal of macro magic itself, but at least you are rid of self-defined macros in the class itself. Something that uses #2 and #3 would have to look somewhat like (i make the name longer to demonstate the restrictions of the approach)

    class ABCDE: public Base<ABCDE, 'ABCD', 'E'> { /* ... */ }
    
  • having the string as a static data member constant with a predefined name. This is an option that fits well together with CRTP, if you have to use it anyways:

    template <class T>
    class Base {
    public:
      std::string GetClassName() const {
        return T::name;
      };
    };
    
    class ABC : public Base<ABC> {
    public:
      constexpr static char const *  name = "ABC";
    };
    

    In C++03, you would have to use a static const member that has to be defined outside the class definition (i.e. in ABC.cpp)

  • having the string as a nonstatic data member of ABC works similarly:

    template <class T>
    class Base {
    public:
      std::string GetClassName() const {
        return static_cast<T const*>(this)->name;
      };
    };
    
    class ABC : public Base<ABC> {
    public:
      const std::string name = "ABC";
    };
    

    This requires C++11 for the in-class member initialization. In C++03 the name has to be properly initialized in every constructor.

  • having the string as data member of the base class works and should be preferred imo, if you don't have type-dependent members, i.e. if you don't need CRTP to do #2:

    class Base {
    public:
      Base(std::string nm) : name(nm) {}
      std::string GetClassName() const {
        return name;
      };
      std::string name;
    };
    
    class ABC : public Base {
    public:
      ABC() : Base("ABC") {}
      ABC(int i) : ABC() { /*another ctor*/ }
    };
    

    To do the initialization of Base with the class name just once you can use the C++11 delegating constructors, although it's not much different writing Base("ABC") or ABC() in each initializer list.

Community
  • 1
  • 1
Arne Mertz
  • 24,171
  • 3
  • 51
  • 90
  • Nice answer. In my answer I have one method of passing strings that you didn't covered (althought your answer is really thorough) – Synxis Feb 27 '14 at 10:45
  • @Synxis thanks. As far as I can see your method of passing a string is the `NAME` macro you use, which is what I wanted to avoid since the OP asked about avoiding macros. – Arne Mertz Feb 27 '14 at 10:53
  • Thank you for answering, I accept Synxis's answer because he posted earlier, hope you dont mind. Besides, the `CLASS_DEF(ABC)` you mentioned is what I want to avoid, because I dont want to hide real codes inside macro, but the CRTP technique is still exactly what I want, thanks! – Marson Mao Mar 04 '14 at 11:03
0

If you just need to get the type's name, you can use typeinfo::name() method.

You could use CRTP, which contains everything that you need. Something like this :

template< typename T >
class MyBase
{
  public:
    MyBase() : className( typeid(T).name() ){}
    virtual ~MyBase(){}


    std::string className;
    // common interfaces, like Initialize()
};

then use it :

class A : public MyBase<A>
{
};
BЈовић
  • 62,405
  • 41
  • 173
  • 273
  • This is true that you can use RTTI to get the name of the type, but the inconvenient is that there is an impact on the performances (that depends on the implementation), and also that it is not really portable. Here, if `T` was `int`, gcc would give the name `i`, not `int`. – Synxis Feb 27 '14 at 10:15
  • @Synxis The name would still be unique. Also, the performances are not an issue, unless you create 10s of thousands of objects per second. – BЈовић Feb 27 '14 at 10:36
  • Actually, names are not required to be unique. See [here](http://stackoverflow.com/questions/4465872/why-typeid-name-returns-weird-characters-using-gcc) for example. – Synxis Feb 27 '14 at 10:43
  • @Synxis Right. Although it is implementation defined, I am yet to see an implementation returning not unique names. As far as I know, all modern mainstream compilers supporting c++11 have unique strings returned. – BЈовић Feb 27 '14 at 11:13