问题
I developed some kind of meta object mechanism in a project, in order to associate type names and properties of any type to an object (see the result here). I have a dirty code working and I'm trying to make it cleaner. Given the following dummy structures:
struct A
{
using Self = A;
using Base = void;
static std::string get_type_name(){ return { "A" }; }
static std::vector<int> get_properties(){ return { 0 }; }
};
#define SELF(class_name)\
using Base = Self;\
using Self = class_name;
struct AA : A
{
SELF(AA)
static std::string get_type_name() { return { "AA" }; }
};
struct AAA : AA
{
SELF(AAA)
static std::string get_type_name(){ return { "AAA" }; }
static std::vector<int> get_properties(){ return { 2, 1 }; }
};
I ended up with this code to get the type names of an object across its hierarchy:
// when the type has no Base member:
template<
typename T,
typename std::enable_if<std::is_same<typename T::Base, void>::value>::type* = nullptr
>
typename std::vector<decltype(T::Self::get_type_name())> get_type_names()
{
return { T::Self::get_type_name() };
}
// when the type has a Base member:
template<
typename T,
typename std::enable_if<!std::is_same<typename T::Base, void>::value>::type* = nullptr
>
typename std::vector<decltype(T::Self::get_type_name())> get_type_names()
{
auto data = get_type_names<typename T::Base>();
data.insert(data.begin(), T::Self::get_type_name());
return data;
}
And something similar for the properties:
template<
typename T,
typename std::enable_if<std::is_same<typename T::Base, void>::value>::type* = nullptr
>
decltype(T::Self::get_properties()) get_properties()
{
return { T::Self::get_properties() };
}
template<
typename T,
typename std::enable_if<!std::is_same<typename T::Base, void>::value>::type* = nullptr
>
decltype(T::Self::get_properties()) get_properties()
{
auto data = get_properties<typename T::Base>();
auto self_data = T::Self::get_properties();
data.insert(data.begin(), self_data.begin(), self_data.end());
return data;
}
When testing the code with this snippet:
template<typename T>
void print()
{
std::cout << T::get_type_name() << std::endl << "\thas types:" << std::endl;
for(auto type_name : get_type_names<T>())
{
std::cout << "\t\t" << type_name << std::endl;
}
std::cout << "\thas properties:" << std::endl;
for(auto property : get_properties<T>())
{
std::cout << "\t\t" << property << std::endl;
}
}
int main()
{
print<A>();
print<AA>();
print<AAA>();
return 0;
}
I get the following output:
A
has types:
A
has properties:
0
AA
has types:
AA
A
has properties:
0
0
AAA
has types:
AAA
AA
A
has properties:
2
1
0
0
This first prototype works well for the names, but as soon as an object is declared without properties, the ones from its base are duplicated. Does somebody see a simple way to correct the problem?
Bonus question: implementations for the meta functions are very similar, does somebody have an hint on how I could factorize the code?
A complete live example is available here
回答1:
Here's an incomplete solution that will put you on the right track. First, everything is dependent on walking the list of the bases. So let's factor that into its own metafunction:
template <class... > struct typelist { };
template <class T, class... Bases>
struct get_bases
: get_bases<typename T::Base, Bases..., T>
{ };
template <class... Bases>
struct get_bases<void, Bases...>
{
using type = typelist<Bases...>;
};
template <class T>
using get_bases_t = typename get_bases<T>::type;
So now, get_bases_t<A>
is typelist<A>
and get_bases_t<AAA>
is typelist<AAA, AA, A>
. Now, all of your other functions are just based on walking down that typelist and doing stuff to it. So let's add an easy way to do that:
template <class T> struct tag { using type = T; };
template <class... Ts>
auto for_each(typelist<Ts...>) {
return [](auto&& f){
using swallow = int[];
(void)swallow{0,
(void(f(tag<Ts>{})), 0)...
};
};
}
Now, getting all the properties is a matter of turning all the bases into their corresponding get_properties()
functions, and then calling all the unique ones. You could probably pick out the unique functions at compile-time, but this works too:
template <class T>
std::vector<int> get_properties() {
using bases = get_bases_t<T>;
using F = std::vector<int>(*)();
F last = nullptr;
std::vector<int> props;
for_each(bases{})([&](auto t){
using B = typename decltype(t)::type;
F cur = B::get_properties;
if (cur != last) {
auto next = cur();
props.insert(props.end(), next.begin(), next.end());
last = cur;
}
});
return props;
}
This approach is very straightforward for getting your type names too:
template <class T>
std::vector<std::string> get_type_names()
{
using bases = get_bases_t<T>;
std::vector<std::string> names;
names.reserve(len_v<bases>); // TODO
for_each(bases{})([&](auto t){
names.push_back(decltype(t)::get_type_name());
});
return names;
}
Or, in this case, simply:
template <class... Bs>
std::vector<std::string> get_type_names_impl(typelist<Bs...>) {
return {Bs::get_type_name()...};
}
template <class T>
std::vector<std::string> get_type_names()
{
return get_type_names_impl(get_bases_t<T>);
}
来源:https://stackoverflow.com/questions/39274948/meta-object-using-static-virtual-functions