第三方SDK定义了几个typedef,例如:
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-function-class:
// 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是相同的类型(unsigned char)。我的想法是让预处理器检查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
上述代码的问题是,预处理器似乎无法解析这两个typedef。有没有办法在预处理期间比较两个typedef(正确)?如果没有,有没有办法阻止编译器解析typedef,那么它会接受SDK_BYTE和SDK_BOOLEAN的两个不同的模板特化?如果没有,我仍然可以提供单一模板专业化并使用BOOST_STATIC_ASSERT使编译器失败,如果SDK_BYTE和SDK_BOOLEAN不相等,但有没有更好的方法来解决我的问题?
答案 0 :(得分:7)
如果C ++ 11是您的选项,这里有一些代码说明使用std::enable_if
和std::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;
}
一些注意事项:
type1
和type2
SdkVariant
结构定义为虚拟type1
时输出一个常数(值0),在转换为type2
时输出一个常量(值1)(如果type2
实际上与type1
不同) 要测试它是否符合您的要求,您可以将type2
的定义替换为
typedef int type2;
因此它与type1
的定义相同。它仍然会编译,并且不会出现与任何双重定义相关的错误。
--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
,因为显式特化不是模板 - 它是真正的数据类型,编译器必须处理它。如果type1
和type2
相同,请执行以下操作:
typename std::enable_if<!std::is_same<type1,type2>::value>::type
由于std::enable_if
的工作方式,不存在。因此编译失败,因为它无法实例化此数据类型。
通过为与T
相同的任何类型type2
定义转换器,我们避免type2
的显式实例化,因此我们不强制编译器来处理它。如果type2
实际存在,它将仅处理std::enable_if<...>::type
的模板专精。否则它将完全忽略它,这正是我们想要的。
同样,对于type1
(以及任何进一步的type3
,type4
等),显式实例化将起作用。
我认为值得指出的是,为某种类型T
定义任何类型type
的模板专精是一种技巧适用无论何时由于正式原因而无法使用显式特化,因此您使用部分特化,但您确实只想将其绑定到此类型。例如,除非显式实例化其封闭模板,否则无法显式实例化成员模板。使用std::enable_if
和std::is_same
的组合也可能有帮助。
答案 1 :(得分: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);
};
答案 2 :(得分: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_BOOLEAN
或SDK_BYTE
,现在必须写variant_cast<mySDK_BOOLEAN>(myVariant)
或variant_cast<mySDK_BYTE>(myVariant)
。谢谢Xeo。
答案 3 :(得分:0)
我相信这可以用一个未使用的模板参数和一个未使用但唯一的结构声明作为参数来完成:
template<class T, class>
class variant_cast { /*...*/ };
template<>
SDK_BYTE variant_cast<SDK_BYTE, struct SDK_BYTE_XXX>::operator()/*...*/
template<>
SDK_BOOLEAN variant_cast<SDK_BOOLEAN, struct SDK_BOOLEAN_XXX>::operator()/*...*/