2014-09-01 33 views
3

我想爲不同的Controller創建Frontend模板類。該Frontend應該使用Controller的實施方法(如果可用)(即功能的支持),以及一個默認的,否則,在使用模板參數的實現(如果可用),否則默認爲

template <typename Controller> 
class Frontend 
{ 
    public: 
    something() 
    { 
     // use Controller::something() if possible 
     // else use default implementation 
    } 
}; 
  • Frontend將使用類型特徵內部找出更多關於Controller,
  • 不應要求Controller從任何提供默認實現的基類派生,因爲默認方法實現將需要Frontend的私有信息。

Controller中將會有大約20種方法可能實施。我試圖創建一個提供有關提供實現信息(嵌套)性狀:

// (T: Controller class) 
T::supportsFeature<FeatureClass, ...>::type 

所以Controller::supportsFeature<>必須提供告知具體的實現。如果控制器支持功能,則::typestd::true_type,否則爲std::false_type。爲此,我創建了一個默認結構來禁用任何功能,因此Controller類必須明確啓用它提供的任何功能。這似乎是從Controller攜帶信息到Frontend的一個舒適的方式,但它有兩個主要缺點:

  1. 很難爲他人(誰最終將提供Controller)實施supportsFeature,因爲一個專業化嵌套traits類必須寫

  2. 我不知道我應該怎麼evaulate要素的存在在Frontend::something(),因爲沒有參數取決於功能的存在 - 我不能提供過載是合理的表現(我不想超載std::true_type vs std::false_type,因爲這根本不會爲自己說話)。我可以簡單地使用if語句並依靠編譯器來刪除死代碼。我是不是該?

所以,總結一下:

  • 如何執行有關從模板類參數模板類的方法是否存在信息?
  • 基於這些信息,我該如何正確切換實現?
+2

使用一個* * SFINAE檢查以測試給定的類型是否支持功能(即特徵),然後失敗爲默認值。在這裏搜索這個詞,如果你不熟悉,那裏有幾個問題.. – Nim 2014-09-01 09:32:51

+0

你可以看看[Boost.TTI](http://www.boost.org/doc/libs/1_56_0/libs/ tti/doc/html/index.html),尤其是[成員函數內省](http://www.boost.org/doc/libs/1_56_0/libs/tti/doc/html/the_type_traits_introspection_library/tti_detail_has_member_function.html) – Angew 2014-09-01 09:36:31

+0

@ Angew不幸的是,在這個特定的環境(嵌入式)中,我遇到了一些嚴重的問題,因爲一些宏會污染boost模板參數名稱。但我至少應該嘗試一下。 – Christoph 2014-09-01 09:50:32

回答

1

我用它來查看準確的簽名:

#include <cstdint> 

#define DEFINE_HAS_SIGNATURE(traitsName, funcName, signature)    \ 
    template <typename U>             \ 
    class traitsName              \ 
    {                  \ 
    private:                \ 
     template<typename T, T> struct helper;        \ 
     template<typename T>            \ 
     static std::uint8_t check(helper<signature, &funcName>*);   \ 
     template<typename T> static std::uint16_t check(...);    \ 
    public:                 \ 
     static                \ 
     constexpr bool value = sizeof(check<U>(0)) == sizeof(std::uint8_t); \ 
    } 

DEFINE_HAS_SIGNATURE(has_something, T::something, void (T::*)()); 

然後使用SFINAE(類似):

template <typename Controller> 
class Frontend 
{ 
public: 
    void something() 
    { 
     somethingT<Controller>(); 
    } 

private: 
    template <typename T> 
    typename std::enable_if<has_something<T>::value>::type 
    somethingT() 
    { 
     controller.something(); 
    } 

    template <typename T> 
    typename std::enable_if<!has_something<T>::value>::type 
    somethingT() 
    { 
     // default implementation 
    } 
}; 

或標籤調度(類似):

template <typename Controller> 
class Frontend 
{ 
public: 
    void something() 
    { 
     something(typename std::conditional<has_something<Controller>::value, 
      std::true_type, 
      std::false_type>::type()); 
    } 

private: 
    void something(std::true_type) { controller.something(); } 
    void something(std::false_type) { /* default implementation */ } 
}; 
+0

我不確定如何在你的建議中使用SFINAE,但我在這裏看到了一些看起來很相似的東西:http://www.codeproject.com/Articles/19444/Interface-Detection可能會幫助我理解你的建議,把它使用 – Christoph 2014-09-02 08:38:34

+0

@Christoph:我添加了一些樣本可能會幫助你。 – Jarod42 2014-09-02 09:19:41

+0

我測試了你的第一個例子,即使對於重載('something()'和'something(int)'),對每個過載使用'DEFINE_HAS_SIGNATURE',它都能正常工作。那很棒!潛伏在黑暗中肯定有一些限制,但我想我會發現什麼時候到了。 – Christoph 2014-09-02 09:33:21

1

經過一些嘗試/錯誤,這裏是我想要的一個可接受的解決方案。

使用SFINAE,當然,所有關於是否使用Controller的成員函數或默認函數的扣除都是在編譯時完成的。

控制器類型的實現者唯一需要的是在控制器中將類型定義爲typedef Controller WithSomething;。與你提到的特徵相比,這並不難。


首先聲明Frontend模板類,並且定義每個你20個可調用函數的兩個模板函數。這裏只有兩個foobar

#include <iostream> 
using std::cout; 

template <typename Ctrl> 
class Frontend; 

template <typename Ctrl> 
void call_foo(typename Ctrl::WithFoo & ctrl) { ctrl.foo(); } 

template <typename Ctrl> 
void call_foo(Ctrl & ctrl) { Frontend<Ctrl>::default_foo(ctrl); } 

template <typename Ctrl> 
void call_bar(typename Ctrl::WithBar & ctrl) { ctrl.bar(); } 

template <typename Ctrl> 
void call_bar(Ctrl & ctrl) { Frontend<Ctrl>::default_bar(ctrl); } 

然後用可調用函數定義Frontend函數。在這裏,我將默認實現定義爲靜態成員,但這可以更改。

template <typename Ctrl> 
class Frontend 
{ 
public: 
    typedef Ctrl controller; 

    void foo() { call_foo<Ctrl>(c); } 
    void bar() { call_bar<Ctrl>(c); } 

    static void default_foo(Ctrl & ctrl) { cout<<"Default foo\n"; } 
    static void default_bar(Ctrl & ctrl) { cout<<"Default bar\n"; } 

private: 
    Ctrl c; 

}; 

最後,一些Controller類的例子。一個定義foobar,另一個定義每個只有一個。

struct CtrlFooBar 
{ 
    typedef CtrlFooBar WithFoo; 
    typedef CtrlFooBar WithBar; 
    void foo() { cout<<"CtrlFB foo\n"; } 
    void bar() { cout<<"CtrlFB bar\n"; } 
}; 

struct CtrlFoo 
{ 
    typedef CtrlFoo WithFoo; 
    void foo() { cout<<"CtrlFoo foo\n"; } 
}; 

struct CtrlBar 
{ 
    typedef CtrlBar WithBar; 
    void bar() { cout<<"CtrlBar bar\n"; } 
}; 

使用Frondtend所有這些類,並與int按預期工作。

int main() 
{ 
    Frontend<CtrlFooBar> c2; 
    Frontend<CtrlFoo> cf; 
    Frontend<CtrlBar> cb; 
    Frontend<int>  ci; 

    c2.foo(); 
    c2.bar(); 
    cf.foo(); 
    cf.bar(); 
    cb.foo(); 
    cb.bar(); 
    ci.foo(); 
    ci.bar(); 

    return 0; 
} 

輸出

CtrlFB foo 
CtrlFB bar 
CtrlFoo foo 
Default bar 
Default foo 
CtrlBar bar 
Default foo 
Default bar 
+0

有沒有辦法將'call_xxx()'函數移入類中?我更喜歡他們不是免費的功能。 – Christoph 2014-09-01 14:42:49

+0

我想你可以將'call_xxx()'函數定義爲模板成員函數(使用另一個模板類型名稱而不是Ctrl,因爲它已經用於類模板類型名稱)。你爲什麼不想讓它們成爲免費的功能? – 2014-09-02 14:04:41

+0

我不希望它們成爲免費的功能,因爲這會使它們遠離「行動地點」。我還必須爲那些有參數的方法傳播所有必要的類型。 – Christoph 2014-09-02 20:10:33

相關問題