2012-08-16 14 views
4

第三方SDK定義了幾種類型定義,如:如何爲同一類型的typedefs提供模板特化?

typedef unsigned char SDK_BYTE 
typedef double SDK_DOUBLE 
typedef unsigned char SDK_BOOLEAN 

它還定義了變量類型SdkVariant:

class SdkVariant 
{ 
public: 
    enum SdkType { SdkByte, SdkDouble, SdkBoolean }; 
    bool toByte(SDK_BYTE&); 
    bool toDouble(SDK_DOUBLE&); 
    bool toBool(SDK_BOOLEAN&); 
    SdkType type(); 
}; 

檢索從這種變型的值看起來像這樣(假定,我們知道所含值的類型):

SdkVariant variant(foobar()); 
double value; 
bool res = variant.toDouble(value); 
if (!res) 
    diePainfully(); 
else 
    doSomethingWith(value); 

這是相當冗長,因此我想要提供一個variant_cast功能級是c一個執行值檢索和錯誤處理:

// general interface: 
template<class T> 
class variant_cast 
{ 
public: 
    T operator()(const SdkVariant& variant); 
}; 

// template specializations: 
template<> 
SDK_DOUBLE variant_cast<SDK_DOUBLE>::operator()(const SdkVariant& variant) 
{ 
    SDK_DOUBLE value; 
    bool res = variant.toDouble(value); 
    if (!res) 
     diePainfully(); 
    return value; 
} 

template<> 
SDK_BYTE variant_cast<SDK_BYTE>::operator()(const SdkVariant& variant) 
{ 
    SDK_BYTE value; 
    bool res = variant.toByte(value); 
    if (!res) 
     diePainfully(); 
    return value; 
} 

template<> 
SDK_BOOLEAN variant_cast<SDK_BOOLEAN>::operator()(const SdkVariant& variant) 
{ 
    SDK_BOOLEAN value; 
    bool res = variant.toByte(value); 
    if (!res) 
     diePainfully(); 
    return value; 
} 

這並不編譯(C2995:已定義功能模板),因爲SDK_BYTE和SDK_BOOLEAN是相同的類型(無符號字符)。我的想法是讓預處理器檢查SDK_BYTE和SDK_BOOLEAN是否相同,如果是這樣,請爲兩者定義單個模板專用化。如果它們不同,它應該使用上面兩個獨立的專業化。像這樣:

#if SDK_BYTE == SDK_BOOLEAN 
template<> 
SDK_BYTE variant_cast<SDK_BYTE>::operator()(const SdkVariant& variant) 
{ 
    SDK_BYTE value; 
    bool res; 
    if (variant.type() == SdkByte) 
     res = variant.toByte(value); 
    else 
     res = variant.toBool(value); 
    if (!res) 
     diePainfully(); 
    return value; 
} 
#else 
    // code from above 
#endif 

上述代碼的問題是,預處理器似乎無法解析兩個typedefs。有沒有辦法在預處理過程中比較兩個typedef(正確)?如果沒有,是否有辦法阻止編譯器解析類型定義,以便爲SDK_BYTE和SDK_BOOLEAN接受兩個不同的模板專門化?如果不是,我仍然可以提供單個模板專門化,並且如果SDK_BYTE和SDK_BOOLEAN不相等,則使用BOOST_STATIC_ASSERT使編譯器失敗,但有沒有更好的方法來解決我的問題?

+0

不幸的是,預處理器不理解'typedefs'。 – 2012-08-16 06:53:19

+1

['BOOST_STRONG_TYPEDEF'](http://www.boost.org/doc/libs/1_50_0/boost/strong_typedef.hpp) – Xeo 2012-08-16 07:16:31

+2

您可以使用C++ 11和'std :: enable_if',並結合'std: :is_same'? – jogojapan 2012-08-16 07:34:08

回答

7

如果C++ 11是一個選擇,這裏是一些代碼,說明使用std::enable_ifstd::is_same一個可能的解決方案:

#include <iostream> 
#include <type_traits> 

struct SdkVariant 
{ 
}; 

typedef int type1; 
typedef float type2; 

template <typename T, typename Enable=void> 
class variant_cast 
{ 
public: 
    /* Default implementation of the converter. This is undefined, but 
    you can define it to throw an exception instead. */ 
    T operator()(const SdkVariant &v); 
}; 

/* Conversion for type1. */ 
template <typename T> 
class variant_cast<T,typename std::enable_if<std::is_same<T,type1>::value>::type> 
{ 
public: 
    type1 operator()(const SdkVariant &v) 
    { 
    return type1 { 0 }; 
    } 
}; 

/* Conversion for type2, IF type2 != type1. Otherwise this 
    specialization will never be used. */ 
template <typename T> 
class variant_cast<T,typename std::enable_if< 
     std::is_same<T,type2>::value 
     && !std::is_same<type1,type2>::value>::type> 
{ 
public: 
    type2 operator()(const SdkVariant &v) 
    { 
    return type2 { 1 }; 
    } 
}; 

int main() 
{ 
    variant_cast<type1> vc1; 
    variant_cast<type2> vc2; 
    std::cout << vc1({}) << std::endl; 
    std::cout << vc2({}) << std::endl; 
    return 0; 
} 

的幾個注意事項:

  1. 取而代之的是不同類型的你定義的庫,我只定義了type1type2
  2. 我已經定義了一個空的SdkVariant結構作爲一個虛擬
  3. 由於該虛擬零件是空的,我的轉換並沒有真正轉換任何東西。當轉換爲type1時,它只輸出常量(值0),而在轉換爲type2時(如果type2實際上與type1不同),則輸出常量(值1)。
  4. 測試它是否確實你需要什麼,你可以用

    typedef int type2; 
    

    取代type2定義所以它與定義type1相同。它仍然會編譯,並且不會出現與任何雙重定義相關的錯誤。

  5. 我已經使用GCC 4.7.0和--std=c++11選項測試過了。

備註對使用std::enable_if和部分與顯式模板特

type1用於該轉換器被聲明爲

template <typename T> 
variant_cast<T,typename std::enable_if<std::is_same<T,type1>::value>::type> 

這意味着它是對於任何定義鍵入T即與type1相同 。取而代之的是,我們可以使用一個明確的專業化

template <> 
variant_cast<type1> 

這是簡單得多,而且作品,也

我沒有這樣做的唯一原因是,type2的情況下,它不會工作,因爲type2我們必須檢查它是否是一樣的type1,即我們必須使用std::enable_if

template <> 
class variant_cast<type2, 
    typename std::enable_if<!std::is_same<type1,type2>::value>::type> 

不幸的是,您不能使用std::enable_if進行明確的專業化,因爲顯式專業化不是模板–它是一種真正的數據類型,編譯器必須對其進行處理。如果type1type2是相同的,這一點:

typename std::enable_if<!std::is_same<type1,type2>::value>::type 

不存在的,因爲這樣std::enable_if作品。所以編譯失敗,因爲它不能實例化這種數據類型。

通過定義轉換器任何類型Ttype2我們避免了顯式實例爲type2是一樣的,因此,我們不會強迫編譯器來處理它。如果std::enable_if<...>::type實際存在,它將只處理type2的模板特化。否則,它會簡單地忽略它,這正是我們想要的。

同樣,在type1(和任何進一步的type3,type4等)的情況下,明確的實例化將起作用。

我認爲這是值得指出的是,定義模板特任何類型T是一樣的,有些類型的type伎倆通常適用當你無法使用正式的原因,明確分工,所以你使用了部分專業化,但你真的只想把它綁定到這個類型。例如,成員模板不能顯式實例化,除非它的封閉模板也被顯式實例化。使用std::enable_ifstd::is_same的組合也可能有幫助。

+0

Xeos方法實際適用於兩種情況,SDK_BOOLEAN == SDK_BYTE和SDK_BOOLEAN!= SDK_BYTE。 Xeo,如果您將其作爲答案發布,我會接受它。否則,我會用代碼示例自己回答問題... – 2012-08-16 09:55:28

+0

上面的代碼沒有指定type1和type2的模板特化,所以不會出現原始問題。如果您要指定模板特化,那麼使用enable_if/is_same的方法將不會編譯。每種類型都需要模板專門化,因爲您必須在正文中調用正確的值檢索函數(toBool,toDouble,toByte)。同時拋出意味着在運行時失敗。在編譯時失敗是首選。 – 2012-08-16 11:06:30

+0

@AlexanderTobiasHeinrich'variant_cast'的兩個專長是模板專業化。第一個用於'type1',第二個用於'type2'。他們是部分專業化,而不是明確的專業化,但我不明白爲什麼這應該是一個問題。關於例外情況:這只是覆蓋'type1'和'type2'以外的類型。這與解決原始問題的事實毫無關係,如果您願意,可以將其刪除。關鍵機制(如果'type1'與'type2'相同,不能重新定義'type2'-specialization')在編譯time_時會起作用。 – jogojapan 2012-08-16 14:11:28

1

你可以做這樣的:

SDK_BYTE asByte(SdkVariant & var) 
{ 
    SDK_BYTE byte; 
    bool const ok = var.toByte(byte); 
    if (!ok) diePainfully(); 
    return byte; 
} 

SDK_DOUBLE asDouble(SdkVariant & var) 
{ 
    SDK_DOUBLE d; 
    bool const ok = var.toDouble(d); 
    if (!ok) diePainfully(); 
    return d; 
} 

SDK_BOOLEAN asBoolean(SdkVariant & var) 
{ 
    SDK_BOOLEAN b; 
    bool const ok = var.toBool(b); 
    if (!ok) diePainfully(); 
    return b; 
} 

static const bool byteAndBooleanAreTheSame = std::is_same<SDK_BYTE, SDK_BOOLEAN>::value; 

template <bool b> 
struct VariantCastImpl 
{ 
    template <typename T> T cast(SdkVariant & var) const; 

    template <> SDK_DOUBLE cast(SdkVariant & var) const { return asDouble(var); } 
    template <> SDK_BYTE cast(SdkVariant & var) const { return asByte(var); } 
    template <> SDK_BOOLEAN cast(SdkVariant & var) const { return asBoolean(var); } 
}; 

template <> 
struct VariantCastImpl<false> 
{ 
    template <typename T> T cast(SdkVariant & var) const; 

    template <> SDK_DOUBLE cast(SdkVariant & var) const { return asDouble(var); } 
    template <> SDK_BYTE cast(SdkVariant & var) const 
    { 
    if (var.type() == SdkVariant::SdkByte) 
    { 
     return asByte(var); 
    } 
    else if (var.type() == SdkVariant::SdkBoolean) 
    { 
     return asBoolean(var); 
    } 
    else 
    { 
     diePainfully(); 
     return SDK_BYTE(); // dummy return, I assume diePainfully throws something 
    } 
    } 
}; 

template <typename T> 
T variant_cast(SdkVariant & var) 
{ 
    return VariantCastImpl<!byteAndBooleanAreTheSame>().cast<T>(var); 
}; 
+0

爲值檢索編寫幾個專門的函數是一種有效的方法,但不需要模板類。前三個函數(asByte,asDouble,asBoolean)就足夠了。 – 2012-08-16 11:23:39

+0

您可以在泛型代碼中使用variant_cast ,而不能使用三個asXXX函數執行相同操作。除此之外,我同意只有擁有這三項功能,一般來說就足以解決您的問題。我不得不承認,我認爲你的問題也是一個挑戰;) – MadScientist 2012-08-16 11:33:32

0

只要是完整的,使用BOOST_STRONG_TYPEDEF的代碼看起來是這樣的:

BOOST_STRONG_TYPEDEF(SDK_BYTE, mySDK_BYTE) 
BOOST_STRONG_TYPEDEF(SDK_BOOLEAN, mySDK_BOOLEAN) 

template<> 
mySDK_BYTE variant_cast<mySDK_BYTE>::operator()(const SdkVariant& variant) 
{ 
    SDK_BYTE value; 
    bool res = variant.toByte(value); 
    if (!res) 
     diePainfully(); 
    return value; 
} 

template<> 
mySDK_BOOLEAN variant_cast<mySDK_BOOLEAN>::operator()(const SdkVariant& variant) 
{ 
    SDK_BOOLEAN value; 
    bool res = variant.toByte(value); 
    if (!res) 
     diePainfully(); 
    return value; 
} 

這實際工作。唯一的缺點是,對於檢索SDK_BOOLEANSDK_BYTE,現在必須編寫variant_cast<mySDK_BOOLEAN>(myVariant)variant_cast<mySDK_BYTE>(myVariant)。謝謝Xeo。

相關問題