4

第 3 方 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 是相同的类型(无符号字符)。我现在的想法是让预处理器检查 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 的两种不同的模板特化?如果没有,如果 SDK_BYTE 和 SDK_BOOLEAN 不相等,我仍然可以提供单一模板专业化并使用 BOOST_STATIC_ASSERT 使编译器失败,但是有没有更好的方法来解决我的问题?

4

4 回答 4

7

如果您可以选择 C++11,这里有一些代码说明了使用std::enable_ifand的可能解决方案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;
}

几点注意事项:

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

    typedef int 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,我们不会强制编译器处理它。它只会处理模板专业化type2是否std::enable_if<...>::type实际存在。否则它将简单地忽略它,这正是我们想要的。

同样,在type1(以及任何进一步type3type4)的情况下,显式实例化将起作用。

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

于 2012-08-16T09:30:33.430 回答
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);
};
于 2012-08-16T10:31:07.737 回答
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_BOOLEANorSDK_BYTE必须写variant_cast<mySDK_BOOLEAN>(myVariant)or variant_cast<mySDK_BYTE>(myVariant)。谢谢Xeo。

于 2012-08-16T11:14:33.610 回答
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()/*...*/
于 2021-01-19T04:45:08.703 回答