2017-05-23 119 views
1

這是一個類似於Using std::forward on sub fields的問題,但答案似乎並不適用於我的情況。在鑄造參數上使用std :: forward

Consider this code:

template<class Base, class F> 
void visit(Base&&, const F&) { 
    throw std::bad_cast(); 
} 

template<class Derived, class... Rest, class Base, class F> 
void visit(Base&& base, const F& f) { 
    if (auto *as_derived = dynamic_cast<Derived *>(&base)) { 
     return f(std::forward<Base>(*as_derived)); 
    } else { 
     return visit<Rest...>(std::forward<Base>(base), f); 
    } 
} 

我的目標是,下面的測試情況下工作:

struct Animal { 
    virtual ~Animal() {} 
}; 
struct Cat : Animal { 
    void speak() & { puts("meow"); } 
    void yowl() && { puts("MEOW!"); } 
}; 
struct Dog : Animal { 
    void speak() & { puts("woof"); } 
    void yowl() && { puts("WOOF!"); } 
}; 

int main() { 
    Animal *a = new Cat(); 
    Animal *b = new Dog(); 
    visit<Cat, Dog>(*a, [](auto&& a){ std::forward<decltype(a)>(a).speak(); }); 
    visit<Cat, Dog>(*b, [](auto&& a){ std::forward<decltype(a)>(a).speak(); }); 
    visit<Cat, Dog>(std::move(*a), [](auto&& a){ std::forward<decltype(a)>(a).yowl(); }); 
    visit<Cat, Dog>(std::move(*b), [](auto&& a){ std::forward<decltype(a)>(a).yowl(); }); 
} 

所需的輸出: 「喵」 「喵」 「汪汪」 「緯!」。注意功能speakyowl是非虛擬的;這在我的原始代碼中是必需的,因爲它們實際上是模板,並且模板不能是虛擬的。

這段代碼寫在這裏的問題是,std::forward<Base>(*as_derived)不僅僅改變*as_derived上的ref-qualifiers和const限定符來實現完美的轉發;它實際上會將類型備份到Base&,削弱visit的整點!

有沒有一個標準庫函數做什麼,我想std::forward的事 - 即,更改*as_derived裁判,預選賽和const修飾詞相匹配的那些將是完美的,從前方推導std::forward<Base>

如果沒有標準的庫函數,我怎麼能寫一個「完美的向前的子類型」函數供我自己使用?

上面的Wandbox鏈接包含了一些對這個測試用例「起作用」的東西,但它並不保留常量,並且它看起來並不優雅。

回答

4

標準中沒有任何內容。但是寫起來並不難。只是煩人。你需要做的是寫一個特質,讓您能夠傳遞到forward類型 - 基本上你要匹配的CV-資格和Derived參考岬什麼Base是什麼,然後傳遞類型爲forward

return f(std::forward<match_ref_t<Base, Derived>>(*as_derived)); 

一個簡單的實現,可以幾乎肯定會更加簡潔,僅僅是:

template <class From, class To> 
struct match_ref { 
    using type = To; 
}; 

template <class From, class To> 
using match_ref_t = typename match_ref<From, To>::type; 

template <class From, class To> 
struct match_ref<From&, To> { 
    using type = match_ref_t<From, To>&; 
}; 

template <class From, class To> 
struct match_ref<From&&, To> { 
    using type = match_ref_t<From, To>&&; 
}; 

template <class From, class To> 
struct match_ref<From const, To> { 
    using type = match_ref_t<From, To> const; 
}; 

template <class From, class To> 
struct match_ref<From volatile, To> { 
    using type = match_ref_t<From, To> volatile; 
}; 

template <class From, class To> 
struct match_ref<From const volatile, To> { 
    using type = match_ref_t<From, To> const volatile; 
}; 

或者,我想:

template <class Check, template <class> class F, class T> 
using maybe_apply = std::conditional_t<Check::value, F<T>, T>; 

template <class From, class To> 
struct match_ref { 
    using non_ref = std::remove_reference_t<From>; 
    using to_cv = maybe_apply<std::is_const<non_ref>, std::add_const_t, 
      maybe_apply<std::is_volatile<non_ref>, std::add_volatile_t, 
      To>>; 

    using type = std::conditional_t< 
     std::is_lvalue_reference<From>::value, 
     to_cv&, 
     std::conditional_t< 
      std::is_rvalue_reference<From>::value, 
      to_cv&&, 
      to_cv> 
     >; 
}; 

template <class From, class To> 
using match_ref_t = typename match_ref<From, To>::type; 
2

轉發只是一個有條件的舉動。

template<bool b> 
struct move_if_t{ 
    template<class T> 
    T&& operator()(T&t)const{ return std::move(t); } 
}; 
template<> 
struct move_if_t<false>{ 
    template<class T> 
    T& operator()(T&t)const{ return t; } 
}; 
template<bool b, class T> 
decltype(auto) move_if(T& t){ 
    return move_if_t<b>{}(t); 
} 

現在,我們得到

template<class Derived, class... Rest, class Base, class F> 
void visit(Base&& base, const F& f) { 
    if (auto *as_derived = dynamic_cast<Derived *>(&base)) { 
    return f(move_if<!std::is_lvalue_reference<Base>{}>(*as_derived)); 
    } else { 
    return visit<Rest...>(std::forward<Base>(base), f); 
    } 
} 
+0

這比列舉所有cv-ref的東西好得多。 – Barry

0

Yakk的回答是令人欽佩的簡潔,似乎工作,但我結束了巴里的實踐回答下去,因爲我發現match_cvref_t是更容易推理比任何替代品。此外,在我的特殊情況下,我最終需要參考match_cvref_t,才能正確執行實際的鑄造操作。因此:

template<class Base, class F> 
void visit(Base&&, const F&) { 
    throw std::bad_cast(); 
} 

template<class DerivedClass, class... Rest, class Base, class F> 
void visit(Base&& base, const F& f) { 
    if (typeid(base) == typeid(DerivedClass)) { 
     using Derived = match_cvref_t<Base, DerivedClass>; 
     return f(std::forward<Derived>(static_cast<Derived&&>(base))); 
    } else { 
     return visit<Rest...>(std::forward<Base>(base), f); 
    } 
} 

我還是設法縮小match_cvref_t

template<class From, class To> 
using match_cvref_t = match_ref_t< 
    From, 
    match_cv_t< 
     std::remove_reference_t<From>, 
     std::remove_reference_t<To> 
    > 
>; 

其中match_cv_tmatch_ref_t約需5的每行代碼。