A C++ idiom for per-class data, accessible without needing virtual getter methods

237 Views Asked by At

(This question is related to reflection, but isn't really about reflection)

I have this hierarchy of classes (say class A and class B : public A), and in addition to instance-specific data, I'd like to have class-specific data shared by all instances. For example, suppose I want to have a FunnyClassName string for each of my classes.

I would like to be able to have non-virtual getters for my per-class data, such as:

/*can it be static? */ const std::string& A::GetFunnyName();

and the most important thing is that I want to have no, or as little as possible, boilerplate code in inheriting classes. The getters are to be implemented once in class A (the root of the class hierarchy); class B should specify its FunnyClassName some other way.

It's been suggested (e.g. indirectly in questions here on SO) that a Multiton object, using the class' type hash as a key, might be the basis of a reasonable solution. Is that the case? Is there 'standard' code which does this (e.g. in the STL or in Boost)? Is there another relevant approach?

Notes:

  • Think this is impossible? See this question and this (laconic) answer. But as some commenters and responders suggest, it may be necessary to have non-static getters, and make the weaker constraint of not having to rewrite the getter for every class (i.e. using RTTI).
  • If C++ had static virtual data members, this would be trivial - virtual static const std::string FunnyName. With static virtual methods it would also be possible but only if we drop our demand for the getter only being implemented in the base class. We would have something like /* static??*/ static const std::string& A::GetFunnyName() { return "Aye"; } and /* static??*/ const std::string& B::GetFunnyName() { return "Bee"; }.
  • I'm not particularly interested in the case of templated classes, but if you want to address that as well, that's nice.
  • FunnyName() is just an example. It could be const Thing& GetFunnyThing(). I don't want anything like the class' name from typeid.name(), or its demangling, or such.
  • C++11 is ok, but a solution in C++03 would be better. No C++14 please.
3

There are 3 best solutions below

1
On

I am not sure this answers the question, but you should consider using typeid. This is a part of RTTI, so it can distinguish static and dynamic types.

Have the following code in your base class:

struct A {
    ...
    std::string GetFunnyName() {return typeid(*this).name();}
};

The strings returned for different derived classes will be different; however, you don't have any control on what these strings will look like (they may contain e.g. a mangled version of the type name).

You may want to use a std::map to translate these system-generated names into more preferred ones like FunnyName1, FunnyName2 etc, but you cannot extract the name of the derived class (or maybe you can, but not in a portable way).

Here is a demo.


Edit: since you really want to work with FunnyThing and not with FunnyName, you should definitely use a map. Make it a static object:

struct A {
private:
    static std::map<std::string, Thing*> my_map;
    ...
}

Then use it to convert string to Thing:

struct A {
    ...
public:
    Thing& GetFunnyThing() {return *my_map[typeid(*this).name()];}
    ...
};

Now each derived class should use RegisterThing to "declare" which Thing it wants to return.

struct A {
    ...
protected:
    static void RegisterThing(std::string name, Thing* thing) {my_map[name] = thing;}
    ...
}

Calling this method only once, and at the correct time, can be implemented in different ways (like the Singleton pattern can be), so I don't want to complicate matters by giving an example.

6
On

If you don't want to use virtual, you can use templates. The name of this idiom is Curiously recurring template pattern, and it's used in ATL and WTL.

Look code.

#include <iostream>
#include <string>

template <typename C>
class Super
{
public:
    std::string GetFunnyName() const
    {
        C *thiz = static_cast<C *>(this);
        return thiz->GetFunnyName();
    }
};
class A : public Super<A>
{
public:
    std::string GetFunnyName() const
    {
        return "A";
    }
};
class B : public Super<B>
{
public:
    std::string GetFunnyName() const
    {
        return "B";
    }
};

template <typename TSuper>
void OutputFunny(const TSuper &obj)
{
    std::cout << obj.GetFunnyName() << "\n";
}

int main()
{
    A a;
    B b;

    OutputFunny(a);
    OutputFunny(b);
}

(live example)

If you want to make B inherit A, code like this:

template <typename C>
class A_base : public Super<C>
{
    ...
};
class A : public A_base<A> { };

class B : public A_base<B>
{
    ...
};

(live example)

My example code uses compile-time polymorphism. So, it cannot be applied in runtime. If you want to get "FunnyName" in runtime, you should use virtual, run-time polymorphism.


Curiously recurring template pattern works like this:

You may see the basic form of the pattern.

template <typename C>
class Super
{
    void foo()
    {
        C *thiz = static_cast<C *>(this);
        thiz->foo();
    }
    ...
};

class Derived : public Super<Derived>
{
    void foo()
    {
        std::cout << "fooo!!\n";
    }
    ...
};

The derived class inherit Super, with Derived itself as template parameter.

Super<Derived> is concretized like this:

template <>
class Super<Derived>
{
    void foo()
    {
        Derived *thiz = static_cast<Derived *>(this); // 1
        thiz->foo();                                  // 2
    }
};

On 1, we're casting this pointer into Derived *, and call foo with this casted pointer on 2. Since the type of pointer is Derived *, thiz->foo(); statement will call Derived::foo.

(wikipedia page explanation seems good)

5
On

Your (original) question is ill-posed (or can be answered by this is impossible). One the one hand you want The getters are to be implemented once in class A (the root of the class hierarchy) and that's that. On the other hand you suggest that If C++ had static virtual data members, this would be trivial. However, with static virtual methods you still would need to re-implement the getters for each derived class, contradicting your first request.

I have implemented some code with the same aim, i.e. giving a nice name description for each class.

namespace some_namespace {
  /// demangles symbol name as returned by typeid(T).name()
  std::string demangle(const char*mangled_name);
  inline std::string demangle(std::string const&mangled_name)
  { return demangle(mangled_name.c_str()); }

  /// provides the name for any type
  template<typename T>
  struct name_traits
  {
  private:
    template<typename U, U> struct check;
    template<typename U>
    static std::true_type test(check<std::string(*)(), &U::name_of_type>*);
    template<typename U>
    static std::false_type test(...);
    //  NOTE what_type required to trick icpc 14.0.2 to compile
    typedef decltype(test<T>(0)) what_type;
    /// true if static std::string T::name_of_type()  exists
    static constexpr bool has_name_of_type = what_type::value;
    /// return name of types with static std::string name_of_type()
    template<bool S>
    static enable_if_t< S, std::string>
    name_t() { return T::name_of_type(); }
    /// return name of all other types: demangle typeid(T).name();
    template<bool S>
    static enable_if_t<!S, std::string>
    name_t()
    { return demangle(typeid(T).name()); }
  public:
    static std::string name()
    { return name_t<has_name_of_type>(); }
  };
}
/// macro  returning the name of a given type.
#define nameof(TYPE) some_namespace::name_traits<TYPE>::name()

Here, any type A may be equipped with std::string A::name_of_type(); to provide the info, or a specialisation struct some_namespace::name_traits<A> may given. If neither is the case, the name is picked up from demangling the typeid.