meta object using “static virtual” functions

时光总嘲笑我的痴心妄想 提交于 2020-01-24 09:25:05

问题


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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!