Reputation: 131950
(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:
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"; }
.const Thing& GetFunnyThing()
. I don't want anything like the class' name from typeid.name(), or its demangling, or such.Upvotes: 0
Views: 236
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
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
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);
}
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>
{
...
};
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