2015-09-02 55 views
1
選擇模板返回類型

我有一些工作,但它似乎非常詳細。從參數

#include <array> 
#include <iostream> 
#include <type_traits> 

using DataArrayShort = std::array<unsigned char, 4>; 
using DataArrayLong = std::array<unsigned char, 11>; 

// Two base classes the later template stuff should choose between 
class Short 
{ 
public: 
    Short(const DataArrayShort & data) { /* do some init */} 
}; 

class Long 
{ 
public: 
    Long(const DataArrayLong & data) { /* do some init */} 
}; 

// Concrete derived of the two bases 
class S1 : public Short 
{ 
public: 
    using Short::Short; 
    operator std::string() { return "S1!";} 
}; 

class S2 : public Short 
{ 
public: 
    using Short::Short; 
    operator std::string() { return "S2!";} 
}; 

class L1 : public Long 
{ 
public: 
    using Long::Long; 
    operator std::string() { return "L1!";} 
}; 

class L2 : public Long 
{ 
public: 
    using Long::Long; 
    operator std::string() { return "L2!";} 
}; 

// Variables that will be modified by parsing other things before calling parse<>() 
bool shortDataSet = false; 
bool longDataSet = false; 
DataArrayShort shortData; 
DataArrayLong longData; 

// Begin overly verbose template stuff 
template<bool IsShort, bool IsLong> 
bool getFlag(); 

template<> 
bool getFlag<true, false>() 
{ 
    return shortDataSet; 
} 

template<> 
bool getFlag<false, true>() 
{ 
    return longDataSet; 
} 


template<bool IsShort, bool IsLong> 
struct RetType 
{}; 

template<> 
struct RetType<true, false> 
{ 
    typedef DataArrayShort & type; 
}; 

template<> 
struct RetType<false, true> 
{ 
    typedef DataArrayLong & type; 
}; 

template<bool IsShort, bool IsLong> 
typename RetType<IsShort, IsLong>::type getData(); 

template<> 
DataArrayShort & getData<true, false>() 
{ 
    return shortData; 
} 

template<> 
DataArrayLong & getData<false, true>() 
{ 
    return longData; 
} 

template<typename T> 
inline std::string parse() 
{ 
    // First test if I can create the type with initialized data 
    if  (getFlag<std::is_base_of<Short, T>::value, std::is_base_of<Long, T>::value>()) 
    { 
     // If it's initialized, Then create it with the correct array 
     T t(getData<std::is_base_of<Short, T>::value, std::is_base_of<Long, T>::value>()); 
     return t; 
    } 
    else 
    { 
     return "with uninitialized data"; 
    } 
} 
// End overly verbose template stuff 

int main(int argc, const char * argv[]) 
{ 
    // Something things that may or may not set shortDataSet and longDataSet and give shortData and longData values 

    std::cout << parse<S1>() << std::endl; 

    shortDataSet = true; 

    std::cout << parse<S1>() << std::endl; 

    std::cout << parse<L2>() << std::endl; 

    longDataSet = true; 

    std::cout << parse<L2>() << std::endl; 
} 

對我很重要的語法是parse()。在解析中,我想確保路由到正確的標誌和數據來實例化ConcreteType。

我開始認爲我不能使用函數模板來做我想做的事情 - 我最好使用具有靜態函數成員的類模板。

使用std :: is_base_of似乎笨拙 - 我可以使用內置繼承與重載,而不是基於Short和Long的重載is_base_of?

RetType似乎沒有必要,但似乎沒有其他方法來聲明getData()。

部分難點在於我需要在實例化之前確定要初始化t的數據。

我不喜歡IsShort和IsLong的單獨模板布爾 - 它不會縮放。

我該怎麼辦才能收緊這個呢?

+0

如果您將所有內容集中到一個單獨的類模板中,這樣的事情會更易於管理。 –

回答

0

我可以建議使用特徵技術,就像其他答案一樣。但我的解決方案是更好的方式,它允許該解決方案的scability,我的意思是在你的代碼沒有更多true, false, ...標誌;)

所以從這個評論開始:

// Variables that will be modified by parsing other things before calling parse<>() 

更改你的代碼更具可擴展性版。

首先連接基本類型與數據類型:

template <typename BaseType> 
class BaseDataTypeTraits; 
template <> struct BaseDataTypeTraits<Short> 
{ 
    typedef DataArrayShort DataType; 
}; 
template <> struct BaseDataTypeTraits<Long> 
{ 
    typedef DataArrayLong DataType; 
}; 

然後定義你的基地型性狀:

template <typename BaseType> 
struct BaseParseTypeTraits 
{ 
    static bool dataSet; 
    typedef typename BaseDataTypeTraits<BaseType>::DataType DataType; 
    static DataType data; 
}; 

template <typename BaseType> 
bool BaseParseTypeTraits<BaseType>::dataSet = false; 
template <typename BaseType> 
typename BaseParseTypeTraits<BaseType>::DataType BaseParseTypeTraits<BaseType>::data; 

和解析特徵爲每個特定的基本類型:

template <typename T, typename EnableIf = void> 
class ParseTypeTraits; 

template <typename T> 
class ParseTypeTraits<T, typename std::enable_if<std::is_base_of<Short, T>::value>::type> 
    : public BaseParseTypeTraits<Short> 
{}; 
template <typename T> 
class ParseTypeTraits<T, typename std::enable_if<std::is_base_of<Long, T>::value>::type> 
    : public BaseParseTypeTraits<Long> 
{}; 

而且那麼你的解析與其他「特質」幾乎相同:

template<typename T> 
inline std::string parse() 
{ 
    typedef ParseTypeTraits<T> TTraits; 
    // First test if I can create the type with initialized data 
    if (TTraits::dataSet) 
    { 
     // If it's initialized, Then create it with the correct array 
     T t(TTraits::data); 
     return t; 
    } 
    else 
    { 
     return "with uninitialized data"; 
    } 
} 


int main(int argc, const char * argv[]) 
{ 
    // Something things that may or may not set shortDataSet and longDataSet and give shortData and longData values 

    std::cout << parse<S1>() << std::endl; 

    BaseParseTypeTraits<Short>::dataSet = true; 

    std::cout << parse<S1>() << std::endl; 

    std::cout << parse<L2>() << std::endl; 

    BaseParseTypeTraits<Long>::dataSet = true; 

    std::cout << parse<L2>() << std::endl; 
} 

工作實施例:ideone

[UPDATE]

在此示例代碼我還添加什麼是需要添加新的基本和數據類型。

我的意思是你有這樣的:

using DataArrayNew = std::array<unsigned char, 200>; 
class New 
{ 
public: 
    New(const DataArrayNew & data) { /* do some init */} 
}; 
class N1 : public New 
{ 
public: 
    using New::New; 
    operator std::string() { return "N1!";} 
}; 

而爲了讓這些類型由您解析的支持 - 你只需要兩個專業化:

template <> struct BaseDataTypeTraits<New> 
{ 
    typedef DataArrayNew DataType; 
}; 
template <typename T> 
class ParseTypeTraits<T, typename std::enable_if<std::is_base_of<New, T>::value>::type> 
    : public BaseParseTypeTraits<New> 
{}; 

這可以被封閉在一個宏:

#define DEFINE_PARSE_TRAITS_TYPE(BaseTypeParam, DataTypeParam) \ 
template <> struct BaseDataTypeTraits<BaseTypeParam>   \ 
{                \ 
    typedef DataTypeParam DataType;       \ 
};                \ 
template <typename T>           \ 
class ParseTypeTraits<T,          \ 
    typename std::enable_if<          \ 
      std::is_base_of<BaseTypeParam, T>::value>::type> \ 
    : public BaseParseTypeTraits<BaseTypeParam>     \ 
{} 

因此,對於新類型的支持很簡單,只要這樣的:

DEFINE_PARSE_TRAITS_TYPE(New, DataArrayNew); 

更簡化的時候可以達到我們要求的基本類型有其數據類型的類定義中定義的 - 喜歡這裏:

class New 
{ 
public: 
    typedef DataArrayNew DataType; 
    New(const DataArrayNew & data) { /* do some init */} 
}; 

然後我們就可以有通用BaseDataTypeTraits定義:

template <typename BaseType> 
struct BaseDataTypeTraits 
{ 
    typedef typename BaseType::DataType DataType; 
}; 

所以對於新型號 - 您只需要爲DataTypeTraits添加專業化:

template <typename T> 
class ParseTypeTraits<T, typename std::enable_if<std::is_base_of<New, T>::value>::type> 
    : public BaseParseTypeTraits<New> 
{}; 
+0

我最終實現的是最接近你的,但略有不同 - 'template struct BaseParseTypeTraits { static bool dataSet; typedef T dataType; 靜態T數據; }; template bool BaseParseTypeTraits :: dataSet = false; template typename BaseParseTypeTraits :: dataType BaseParseTypeTraits :: data;'然後專門研究ParseTypeTraits從DataArrayLong或DataArrayShort中派生的位置。我試圖發佈一個修改,但它被拒絕了。 – bizaff

+0

@bizaff我使用了一些改進來編輯我的答案。我還加了一些新的想法...... HTH – PiotrNycz

2

你應該轉發到啓用了SFINAE的調度程序。正向

template <int I> struct chooser : chooser<I-1> { }; 
template <> struct chooser<0> { }; 

它:啓動與繼承樹

template <typename T> 
std::string parse() { return parse_impl<T>(chooser<2>{}); } 

寫你的情況:從Short

template <typename T, 
      typename = std::enable_if_t<std::is_base_of<Short, T>::value> 
      > 
std::string parse_impl(chooser<2>) { // (1) 
    // we're a Short! 
    if (shortDataSet) { 
     return T{shortData}; 
    } 
    else { 
     return "with uninitialized data"; 
    } 
} 

template <typename T, 
      typename = std::enable_if_t<std::is_base_of<Long, T>::value> 
      > 
std::string parse_impl(chooser<1>) { // (2) 
    // we're a Long! 
    if (longDataSet) { 
     return T{longData}; 
    } 
    else { 
     return "with uninitialized data"; 
    } 
}  

template <typename > 
std::string parse_impl(chooser<0>) { // (3) 
    // base case 
    return "with uninitialized data"; 
} 

如果T繼承,(1)被調用。否則,如果它繼承自Long,則調用(2)。否則,調用(3)。這是做SFINAE多個潛在重疊標準的便捷方式

+0

如果它不是Short或Long,它應該編譯失敗。短或長可能會發生未初始化的數據。 – bizaff

+0

@bizaff然後你可以簡單地刪除'(3)'過載。沒有後備,所以它不會編譯。 – Barry

2

重構的一點點走一段很長的路要走(因爲你可以,畢竟,無論從ShortLong權繼承?):

template<class T, bool IsShort = std::is_base_of<Short, T>::value, 
        bool IsLong = std::is_base_of<Long, T>::value> 
struct data_traits { }; 

template<class T> 
struct data_traits<T, true, false> { 
    static bool getFlag() { return shortDataSet; } 
    static DataArrayShort & getData() { return shortData; } 
}; 

template<class T> 
struct data_traits<T, false, true> { 
    static bool getFlag() { return longDataSet; } 
    static DataArrayLong & getData() { return longData; } 
}; 

template<typename T> 
inline std::string parse() 
{ 
    using traits = data_traits<T>; 
    // First test if I can create the type with initialized data 
    if (traits::getFlag()) 
    { 
     // If it's initialized, Then create it with the correct array 
     T t(traits::getData()); 
     return t; 
    } 
    else 
    { 
     return "with uninitialized data"; 
    } 
}