einpoklum
einpoklum

Reputation: 131950

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

(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:

Upvotes: 0

Views: 236

Answers (3)

Walter
Walter

Reputation: 45444

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.

Upvotes: 2

anatolyg
anatolyg

Reputation: 28278

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.

Upvotes: 0

ikh
ikh

Reputation: 10417

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)

Upvotes: 2

Related Questions