2017-08-31 25 views
5

我想實現一個使用lambdas而不實現類的多態訪問者。我已經有了一個基礎,但是我正在努力爲我的lambda的參數扣除類型。帶lambda的多態訪問者

比方說,我有一些遺留代碼庫,決定使用類型標籤多態類型,像這樣:

enum class ClassType 
{ 
    BaseType = 0, TypeA, TypeB 
}; 

class BaseType 
{ 
public: 
    virtual ~BaseType() {} 
    ClassType getType() const 
    { return type; } 

protected: 
    ClassType type; 
}; 

class TypeA : public BaseType 
{ 
public: 
    static const ClassType Type = ClassType::TypeA; 
    explicit TypeA(int val) : val(val) 
    { type = ClassType::TypeA; } 
    virtual ~TypeA() {} 

    int val; 
}; 

class TypeB : public BaseType 
{ 
public: 
    static const ClassType Type = ClassType::TypeB; 
    explicit TypeB(std::string s) : s(s) 
    { type = ClassType::TypeB; } 
    virtual ~TypeB() {} 

    std::string s; 
}; 

我想實現的是類似std::variant遊客那麼看起來像一個遊客這樣的:

std::vector<BaseType*> elements; 
elements.emplace_back(new TypeA(1)); 
elements.emplace_back(new TypeB("hello")); 

for (auto elem : elements) 
{ 
    visit(elem, 
     [](TypeA* typeA) { 
      std::cout << "Found TypeA element, val=" << typeA->val << std::endl; 
     }, 
     [](TypeB* typeB) { 
      std::cout << "Found TypeB element, s=" << typeB->s << std::endl; 
     } 
    ); 
} 

我至今未能實現這樣的功能visit<>()做法是下面的代碼:

template <typename T> 
struct identity 
{ 
    typedef T type; 
}; 

template <typename T> 
void apply_(BaseType* b, typename identity<std::function<void(T*)>&>::type visitor) 
{ 
    if (b->getType() != T::Type) 
     return; 

    T* t = dynamic_cast<T*>(b); 
    if (t) visitor(t); 
} 

template <typename... Ts> 
void visit(BaseType* b, Ts... visitors) { 
    std::initializer_list<int>{ (apply_(b, visitors), 0)... }; 
} 

編譯器抱怨說它不能推導出我的apply_函數的模板參數T

我該如何聲明apply_的正確模板和函數簽名才能正確捕獲lambda表達式,甚至可能還有其他可調用的對象?或者甚至可能是這樣的?

+0

lamdba不是'std :: function'。 – Jarod42

+2

有了一些函數特徵,你可以從'operator()'中檢索參數,假設沒有超載,沒有'auto'。 – Jarod42

+0

順便說一句,如果你寫了一個真正的訪問者模式,你可能有一種方法來創建你的訪問者,就像你爲訪問做的那樣。 – Jarod42

回答

3

下面是一個(不完全)解決方案,使用具有任何功能對象的工作的一元非過載非模板operator()。首先,讓我們創建一個幫手類型別名檢索的第一個參數的類型:

template <typename> 
struct deduce_arg_type; 

template <typename Return, typename X, typename T> 
struct deduce_arg_type<Return(X::*)(T) const> 
{ 
    using type = T; 
}; 

template <typename F> 
using arg_type = typename deduce_arg_type<decltype(&F::operator())>::type; 

然後,我們可以使用一個倍的表達可變參數模板調用任何函數對象爲其dynamic_cast成功:

template <typename Base, typename... Fs> 
void visit(Base* ptr, Fs&&... fs) 
{ 
    const auto attempt = [&](auto&& f) 
    { 
     using f_type = std::decay_t<decltype(f)>; 
     using p_type = arg_type<f_type>; 

     if(auto cp = dynamic_cast<p_type>(ptr); cp != nullptr) 
     { 
      std::forward<decltype(f)>(f)(cp); 
     } 
    }; 

    (attempt(std::forward<Fs>(fs)), ...); 
} 

用例:

int main() 
{ 
    std::vector<std::unique_ptr<Base>> v; 
    v.emplace_back(std::make_unique<A>()); 
    v.emplace_back(std::make_unique<B>()); 
    v.emplace_back(std::make_unique<C>()); 

    for(const auto& p : v) 
    { 
     visit(p.get(), [](const A*){ std::cout << "A"; }, 
         [](const B*){ std::cout << "B"; }, 
         [](const C*){ std::cout << "C"; }); 
    } 
} 

ABC

live example on wandbox

+0

我接受這個答案,因爲事實證明,我目前不得不使用的編譯器還不支持更復雜的包擴展和/或摺疊表達式,這些表達式排除了@ Jarod42的答案。通過對這個解決方案的一些小修改,我可以在我的舊編譯器上編譯它。 –

0

我並不總是這麼說,但是這可能是爲Boost.Preprocessor工作。你有一個類型列表對應於枚舉列表,每個實例通過getType()標識自己。因此,我們可以使用:

#include <boost/preprocessor/seq/for_each.hpp> 

#define CLASS_LIST (TypeA) (TypeB) 

// just take one visitor 
template <class Visitor> 
void visit(Base* ptr, Visitor f) { 
    switch (ptr->getType()) { 
    #define CASE_ST(r, data, elem) case elem: f(static_cast<elem*>(ptr)); break; 
    BOOST_PP_SEQ_FOR_EACH(CASE_ST, ~, CLASS_LIST) 
    #undef CASE_ST 
    default: f(ptr); // in case you want an "else" 
        // this is optional 
    } 
} 

這將預處理到:

switch (ptr->getType()) { 
case TypeA: f(static_cast<TypeA*>(ptr)); break; 
case TypeB: f(static_cast<TypeB*>(ptr)); break; 
default: f(ptr); 
} 
1

假設你不能更改虛擬類,你可以做到以下幾點:

template <typename F> 
decltype(auto) visitBaseType(BaseType& base, F&& f) 
{ 
    switch (base.getType()) 
    { 
     case ClassType::BaseType: return f(base); 
     case ClassType::TypeA: return f(dynamic_cast<TypeA&>(base)); 
     case ClassType::TypeB: return f(dynamic_cast<TypeB&>(base)); 
    } 
    throw std::runtime_error("Bad type"); 
} 

template<class... Ts> struct overloaded : Ts... { 
    using Ts::operator()...; 

    overloaded(Ts... ts) : Ts(ts)... {} 
}; 
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>; 

template <typename ... Fs> 
decltype(auto) visit(BaseType& base, Fs&&... fs) 
{ 
    return visitBaseType(base, overloaded(fs...)); 
} 

Demo