如何为相同类型的typedef提供模板特化?

Ale*_*ler 4 c++ templates typedef c-preprocessor

第三方SDK定义了几个typedef,例如:

typedef unsigned char SDK_BYTE
typedef double SDK_DOUBLE
typedef unsigned char SDK_BOOLEAN
Run Code Online (Sandbox Code Playgroud)

它还定义了一个变体类型SdkVariant:

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

从这样的变量中检索值看起来像这样(假设,我们知道所包含的值的类型):

SdkVariant variant(foobar());
double value;
bool res = variant.toDouble(value);
if (!res)
    diePainfully();
else
    doSomethingWith(value);
Run Code Online (Sandbox Code Playgroud)

这非常冗长,因此我想提供一个可以执行值检索和错误处理的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;
}
Run Code Online (Sandbox Code Playgroud)

这不编译(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
Run Code Online (Sandbox Code Playgroud)

上述代码的问题是,预处理器似乎无法解析这两个typedef.有没有办法在预处理期间比较两个typedef(正确)?如果没有,有没有办法阻止编译器解析typedef,那么它会接受SDK_BYTE和SDK_BOOLEAN的两个不同的模板特化?如果没有,我仍然可以提供单一模板特化并使用BOOST_STATIC_ASSERT使编译器失败,如果SDK_BYTE和SDK_BOOLEAN不相等,但是有更好的方法来解决我的问题吗?

jog*_*pan 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;
}
Run Code Online (Sandbox Code Playgroud)

几点说明:

  1. 而不是你从那个库定义的各种类型,我只定义type1type2
  2. 我已经将一个空SdkVariant结构定义为一个虚拟结构
  3. 因为那个虚拟是空的,我的转换并没有真正转换任何东西.转换为时只输出一个常量(值0),转换为type1常量(值1)时type2(如果type2实际上不同type1).
  4. 要测试它是否满足您的需要,您可以替换type2with 的定义

    typedef int type2;
    
    Run Code Online (Sandbox Code Playgroud)

    所以它与定义相同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>
Run Code Online (Sandbox Code Playgroud)

这意味着它被定义为任何类型T的相同type1.相反,我们可以使用明确的专业化

template <>
variant_cast<type1>
Run Code Online (Sandbox Code Playgroud)

这更简单,也很有效.

我不这样做的唯一原因是,如果type2它不起作用,因为type2我们必须检查它是否相同type1,即我们必须使用std::enable_if:

template <>
class variant_cast<type2,
   typename std::enable_if<!std::is_same<type1,type2>::value>::type>
Run Code Online (Sandbox Code Playgroud)

遗憾的是,您不能std::enable_if在显式特化中使用,因为显式特化不是模板 - 它是真正的数据类型,编译器必须处理它.如果type1type2是相同的,这一点:

typename std::enable_if<!std::is_same<type1,type2>::value>::type
Run Code Online (Sandbox Code Playgroud)

因为工作方式不存在std::enable_if.因此编译失败,因为它无法实例化此数据类型.

通过我们避免显式实例化的任何类型Ttype2定义转换器type2,因此我们不强制编译器处理它.type2如果std::enable_if<...>::type实际存在,它将仅处理模板特化.否则它将完全忽略它,这正是我们想要的.

再次,在的情况下type1(和任何进一步的type3,type4等)的显式实例将工作.

我认为值得指出的是,定义任何类型Ttype的模板特化与某些类型相同是一种技巧,通常适用于您出于正式原因不能使用显式特化,因此您使用部分特化,但是您真的想把它绑定到这一种类型.例如,除非显式实例化其封闭模板,否则无法显式实例化成员模板.使用组合std::enable_ifstd::is_same可能也有帮助.