2012-08-16 50 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。

相关问题