How can I provide custom templates for a typedef of the same type?

Third-party SDK defines several typedef, for example:

typedef unsigned char SDK_BYTE typedef double SDK_DOUBLE typedef unsigned char SDK_BOOLEAN 

It also defines the type of the SdkVariant variant:

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

Getting the value from this option looks like this (suppose we know the type of value contained):

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

This is pretty verbose, so I want to provide a variant_cast-function class that can do a value lookup and error handling:

 // 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; } 

This is not compiled (C2995: the function template is already defined), because SDK_BYTE and SDK_BOOLEAN are the same types (unsigned char). My idea now is to let the preprocessor verify that SDK_BYTE and SDK_BOOLEAN are the same, and if so, define one specialized specialization for both. If they are different, they should use two separate specializations from above. Like this:

 #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 

The problem with the code above is that the preprocessor seems impossible to solve the two types of typedef. Is there a way to compare two typedefs (correctly) during preprocessing? If not, is there a way to prevent the compiler from allowing typedefs to accept two different template specializations for SDK_BYTE and SDK_BOOLEAN? If not, I can provide a separate template specialization and use BOOST_STATIC_ASSERT to compile the compiler if SDK_BYTE and SDK_BOOLEAN are not equal, but is there a better way to solve my problem?

+4
source share
3 answers

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:

  • Instead of the different types that you define with this library, I defined only type1 and type2
  • I defined an empty SdkVariant structure as dummy
  • Since this dummy is empty, my conversion does not really convert anything. It simply prints a constant (value 0) when converting to type1 and a constant (value 1) when converting to type2 (if type2 actually different from type1 ).
  • To check if it does what you need, you can replace the definition of type2 with

     typedef int type2; 

    therefore, it is identical to the definition for type1 . It will still compile, and there will be no error associated with any kind of double definition.

  • I tested this with GCC 4.7.0 and --std=c++11 .

A note on using std::enable_if and partial or explicit custom patterns

The converter for type1 declared as

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

which means that it is defined for any type T that matches type1 . Instead, we could use explicit specialization

 template <> variant_cast<type1> 

which is much simpler, but also works .

The only reason I did not do this is in the case of type2 , it will not work , because for type2 we have to check if this matches type1 , i.e. we should use std::enable_if :

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

Unfortunately, you cannot use std::enable_if in explicit specialization, because explicit specialization is not a template - it is a real data type, and the compiler must process it. If type1 and type2 identical, this is:

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

does not exist, as std::enable_if works. Thus, compilation fails because it cannot create an instance of this data type.

By defining a converter for any type T that matches type2 , we avoid an explicit instance for type2 , so we do not force the compiler to process it. It will only handle template specialization for type2 if std::enable_if<...>::type exists. Otherwise, he will simply ignore what exactly we want.

Again, in the case of type1 (and for any further type3 , type4 , etc.), explicit instantiation will work.

I find it worthwhile to point out that defining template specialization for any type T , similar to some type type , is a trick that is usually applicable when you cannot use explicit specialization for formal reasons, so you use partial specialization, but you really want to bind it only to this type. For example, a member template cannot be explicitly instantiated unless its wrapper template is explicitly created. Using a combination of std::enable_if and std::is_same probably also helps there.

+7
source

You can do it as follows:

 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); }; 
+1
source

To be complete, using BOOST_STRONG_TYPEDEF, the code might 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; } 

It really works. The only drawback is that to extract SDK_BOOLEAN or SDK_BYTE now need to write variant_cast<mySDK_BOOLEAN>(myVariant) or variant_cast<mySDK_BYTE>(myVariant) . Thanks Xeo.

0
source

All Articles