A 3rd party SDK defines several typedefs, e.g.:
typedef unsigned char SDK_BYTE
typedef double SDK_DOUBLE
typedef unsigned char SDK_BOOLEAN
If C++11 is an option for you, here is some code illustrating a possible solution using std::enable_if
and 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;
}
A few notes:
type1
and type2
SdkVariant
struct as a dummytype1
, and a constant (value 1) when converting to type2
(if type2
is actually different from type1
).To test whether it does what you need, you may replace the definition of type2
with
typedef int type2;
so it is identical with the definition for type1
. It will still compile, and there will be no error related to any double definition.
--std=c++11
option.Remark about the use of std::enable_if
and partial vs. explicit template specializations
The converter for type1
is declared as
template <typename T>
variant_cast<T,typename std::enable_if<std::is_same<T,type1>::value>::type>
which means it is defined for any type T
that is the same as type1
. Instead, we could have used an explicit specialization
template <>
variant_cast<type1>
which is much simpler, and works, too.
The only reason I didn't do that is that in the case of type2
it won't work, because for type2
we must check whether it is the same as type1
, i.e. we must use std::enable_if
:
template <>
class variant_cast<type2,
typename std::enable_if<!std::is_same<type1,type2>::value>::type>
Unfortunately, you can't use std::enable_if
in an explicit specialization, because an explicit specialization is not a template – it's a real data type, and the compiler must process it. If type1
and type2
are identical, this:
typename std::enable_if<!std::is_same<type1,type2>::value>::type
does not exist, because of the way std::enable_if
works. So the compilation fails because it can't instantiate this data type.
By defining the converter for any type T
that is the same as type2
we avoid the explicit instantiation for type2
, hence we don't force the compiler to process it. It will only process the template specialization for type2
if the std::enable_if<...>::type
actually exists. Otherwise it will simply ignore it, which is exactly what we want.
Again, in the case of type1
(and for any further type3
, type4
etc.) an explicit instantiation will work.
I think it's worthwhile pointing out that defining a template specialization for any type T
that is the same as some type type
is a trick that is generally applicable whenever you can't use an explicit specialization for formal reasons, so you use a partial specialization, but you really want to bind it to this one type only. For instance, a member template cannot be explicitly instantiated unless its enclosing template is explicitly instantiated, too. Using a combination of std::enable_if
and std::is_same
probably helps there, too.
I believe this can be done with an unused template argument and an unused-but-unique struct declaration as the argument:
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()/*...*/
Just to be complete, using the BOOST_STRONG_TYPEDEF the code could look like this:
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;
}
This actually works. The only downside is, that for retrieving an SDK_BOOLEAN
or SDK_BYTE
one now has to write variant_cast<mySDK_BOOLEAN>(myVariant)
or variant_cast<mySDK_BYTE>(myVariant)
. Thanks Xeo.
You could do it like this:
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);
};