2011-09-27 102 views
4

我有一個模板,template <typename T> class wrapper,我想專門基於typename T::context_type的存在。如果聲明瞭typename T::context_type,那麼wrapper<T>實例化的構造函數和賦值運算符重載應接受強制參數typename T::context_type。此外,wrapper<T>對象會將「上下文」存儲在成員數據中。如果typename T::context_type不存在,那麼wrapper<T>的構造函數和賦值運算符重載只需要少一個參數,並且不會有其他數據成員。是否有可能基於模板類型參數的嵌套typedef的存在來專門化模板定義?

這可能嗎?我可以在不更改config1config2main()的定義的情況下編譯以下代碼:

#include <iostream> 

template <typename T, bool context_type_defined = true> 
class wrapper 
{ 
public: 
    typedef typename T::context_type context_type; 

private: 
    context_type ctx; 

public: 
    wrapper(context_type ctx_) 
     : ctx(ctx_) 
    { 
     std::cout << "T::context_type exists." << std::endl; 
    } 
}; 

template <typename T> 
class wrapper<T, false> 
{ 
public: 
    wrapper() { 
     std::cout << "T::context_type does not exist." << std::endl; 
    } 
}; 

class config1 { 
public: 
    typedef int context_type; 
}; 

class config2 { 
public: 
}; 

int main() 
{ 
    wrapper<config1> w1(0); 
    wrapper<config2> w2; 
} 

回答

3

是的,這是可能的。過去我通過使用一些元編程技巧來實現這種行爲。基本構建塊如下:

BOOST_MPL_HAS_XXX_TRAIT_DEF定義一個元函數謂詞,如果參數是類類型並且具有給定名稱的嵌套類型(在您的情況下爲context_type),則該元函數謂詞將計算爲真類型。

http://www.boost.org/doc/libs/1_47_0/libs/mpl/doc/refmanual/has-xxx-trait-def.html

Boost.EnableIf,定義基於先前定義的性狀特。

http://www.boost.org/libs/utility/enable_if.html#見3.1啓用模板類特


請注意,您可能能夠獲得這種行爲直接與SFINAE工作,這樣的事情可能工作:

template< typename T, typename Context = void > 
class wrapper { ... }; // Base definition 

template< typename T > 
class wrapper< T, typename voif_mfn< typename T::context_type >::type > { ... }; // Specialization 

然而,我就像基於特徵的解決方案的表現力和使能力一樣。

+0

你需要做的像'類型名稱void_ :: type'來確保專業化是有效的*和*第二個參數匹配默認參數'void'。 –

+0

@Luc Danton:我後面的實際想法是,由於SFINAE的緣故,沒有內部'context_type'的類型'T'不會選擇專業化。 –

+0

對於那些沒有嵌套類型的類型來說,特化將是無效的,但是如果'T :: context_type'是'int',那麼特化是'wrappper '並且不匹配主模板'包裝',這就是爲什麼我的意思是「它不匹配」。 –

4

這是可能的,並有很多方法來實現這一點。他們都應該回到has_type的特徵類別has_type<T>::value,如果成員typedef存在,則返回true,否則返回false。我們假設我們已經有了這個特質類。那麼這裏有一個解決方案,使用C++模板11別名:

template <typename T, bool> class FooImpl 
{ 
    // implement general case 
}; 

template <typename T> class FooImpl<T, true> 
{ 
    // implement specific case 
}; 

template <typename T> using Foo = FooImpl<T, has_type<T>::value>; // C++11 only 

現在讓typetrait:

template<typename T> 
struct has_type 
{ 
private: 
    typedef char      yes; 
    typedef struct { char array[2]; } no; 

    template<typename C> static yes test(typename C::context_type*); 
    template<typename C> static no test(...); 
public: 
    static const bool value = sizeof(test<T>(0)) == sizeof(yes); 
}; 

如果你沒有C++ 11,或者如果你不這樣做想要重寫整個班級,你可以使這種區分更加細化,例如通過使用std::enable_ifstd::conditional等發表評論,如果你想要一些具體的例子。

+0

感謝您的回答。當C++ 11可用於我的項目時,模板別名看起來就像是要走的路。但現在,我僅限於C++ 03。目前我正試圖獲得@ K-ballo的建議,但事實證明這很困難。 –

+0

@DanielTrebbien您實際上並不需要模板別名來使用此解決方案。你可以使用古老的繼承技巧:'template struct Foo:FooImpl :: value> {};' –

+0

@David:但是在新的派生類中使用正確的構造函數會很困難。這就是爲什麼我沒有從一開始就繼承一些繼承......我認爲你可以單獨切換構造函數的唯一方法是使用'enable_if'。 –

1

使用@K-ballo's answer,我寫了下面:

namespace detail { 
BOOST_MPL_HAS_XXX_TRAIT_DEF(context_type) 
} 

template <typename T, typename Enable = void> 
class wrapper 
{ 
public: 
    wrapper() { 
     std::cout << "T::context_type does not exist." << std::endl; 
    } 
}; 

template <typename T> 
class wrapper<T, typename boost::enable_if<detail::has_context_type<T> >::type> 
{ 
public: 
    typedef typename T::context_type context_type; 

private: 
    context_type ctx; 

public: 
    wrapper(context_type ctx_) 
     : ctx(ctx_) 
    { 
     std::cout << "T::context_type exists." << std::endl; 
    } 
}; 

現在,示例代碼編譯和輸出:

 
T::context_type exists. 
T::context_type does not exist.