2016-09-01 14 views
3

オブジェクトに型の名前とプロパティを関連付けるために、ある種のメタオブジェクトメカニズムをプロジェクトで開発しました(結果:here参照)。私は汚いコードが働いていると私はそれをきれいにしようとしています。以下のダミー構造体を考える:"static virtual"関数を使用したメタオブジェクト

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 }; } 
}; 

私はその階層間でのオブジェクトのタイプ名を取得するには、このコードになってしまった:

// 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; 
} 

およびプロパティの似たような:

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; 
} 

このスニペットでコードをテストする場合:

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; 
} 

私は、次のような出力が得られます。

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 

この最初のプロトタイプは、名前のためにうまく動作しますが、できるだけ早くオブジェクトはプロパティせずに宣言されているとして、そのベースからのものが複製されます。誰かが問題を解決する簡単な方法を見ていますか?

ボーナスの質問:メタ関数の実装は非常に似ていますが、誰かがコードをどのように分解することができるかのヒントを持っていますか?

A完全なライブの例では、ここでは正しい軌道に乗ってあなたを配置します不完全なソリューションですhere

+0

問題はプロパティで発生しますが、タイプでは発生しません。 – imreal

+0

@imreal問題は型にも発生しますが、私の使用例では常に型が宣言されます。一方、プロパティーはオプションです – wasthishelpful

+0

これは、 'get_type_name'と' get_type_names'の間に違いがありますが、 'get_properties'は同じです。間違ったものが呼び出されるかもしれません。 – imreal

答えて

1

可能です。まず、すべてはベースのリストを歩くことに依存しています。だから聞かせてファクター独自のメタ関数へ:

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; 

はだから今、get_bases_t<A>typelist<A>あるとget_bases_t<AAA>typelist<AAA, AA, A>です。さて、あなたの他のすべての機能は、そのタイプリストを歩いてそれに物事をすることに基づいています。それでは、それを行うための簡単な方法を追加してみましょう。

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)... 
     }; 
    }; 
} 

は今、すべてのプロパティを取得することは、それらに対応するget_properties()関数にすべての拠点を回し、その後、すべてのユニークなものを呼び出すの問題です。あなたはおそらくコンパイル時にユニークな機能を選ぶことができますが、これはあまりにも動作します:

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; 
} 

このアプローチは、あまりにもあなたのタイプ名を取得するための非常に簡単です:

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; 
} 

それとも、この場合、単純に:

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>); 
} 
+0

コードを完全に理解するまで何時間もかかりましたが、完全に機能します。 'for_each'で使用される戻り型の控除は、C++ 14を意味します。それは私にとっては大丈夫です、私はOPにタグを追加しました。どうもありがとう :) – wasthishelpful

関連する問題