【问题标题】:How can I provide template specializations for typedefs of the same type?如何为相同类型的 typedef 提供模板特化?
【发布时间】:2012-08-12 11:57:49
【问题描述】:

第 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 使编译器失败,但是有没有更好的方法来解决我的问题?

【问题讨论】:

  • 很遗憾,预处理器不理解typedefs
  • 你能用C++11和std::enable_if结合std::is_same吗?

标签: c++ templates c-preprocessor typedef


【解决方案1】:

如果 C++11 适合您,这里有一些代码说明了使用 std::enable_ifstd::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. 因为那个 dummy 是空的,所以我的转换并没有真正转换任何东西。转换为type1时只输出一个常量(值0),转换为type2时输出一个常量(值1)(如果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>

这意味着它是为任何与type1 相同的类型T 定义的。相反,我们可以使用显式特化

template <>
variant_cast<type1>

这更简单,并且也可以工作

我没有这样做的唯一原因是type2的情况下它不起作用,因为对于type2,我们必须检查它是否与@987654343相同@,即我们必须使用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 的工作方式。所以编译失败是因为无法实例化这个数据类型。

通过为与type2 相同的任何类型T 定义转换器,我们避免了type2 的显式实例化,因此我们不会强制编译器处理它。如果std::enable_if&lt;...&gt;::type 确实存在,它只会处理type2 的模板特化。否则它会直接忽略它,这正是我们想要的。

同样,对于type1(以及任何其他type3type4 等),显式实例化将起作用。

我认为值得指出的是,为任何类型 T 定义模板特化与某些类型 type 是一个普遍适用的技巧 每当你因为形式原因不能使用显式特化,所以你使用部分特化,但你真的只想把它绑定到这个类型。例如,成员模板不能被显式实例化,除非它的封闭模板也被显式实例化。使用 std::enable_ifstd::is_same 的组合可能也有帮助。

【讨论】:

  • Xeos 方法实际上适用于两种情况,SDK_BOOLEAN == SDK_BYTE 和 SDK_BOOLEAN != SDK_BYTE。 Xeo,如果您将其发布为答案,我会接受。否则,我将用代码示例自己回答这个问题......
  • 上面的代码没有为type1和type2指定模板特化,所以没有出现原来的问题。如果您指定模板特化,则使用 enable_if/is_same 的方法将无法编译。每种类型都需要模板特化,因为您必须在正文中调用正确的值检索函数(toBool、toDouble、toByte)。投掷也意味着在运行时失败。编译时失败是首选。
  • @AlexanderTobiasHeinrich variant_cast 的两个特化是模板特化。第一个为type1,第二个为type2。它们是部分专业化,而不是显式专业化,但我不明白为什么这应该是一个问题。关于例外:这仅涵盖type1type2 以外的类型。这与这是原始问题的解决方案无关,您可以根据需要将其删除。关键机制(如果type1type2 相同,则不重新定义type2-specialization)在编译时起作用。
  • @AlexanderTobiasHeinrich 您在关于 Xeos 方法的第一条评论中是什么意思?上述方法也适用于这两种情况。
  • 我必须道歉。我复制了你的代码并玩了一段时间,并试图在游戏中添加第三种类型,但我做错了。现在我再次对其进行了研究,我必须承认,您的解决方案确实有效,而且很漂亮......接受了!
【解决方案2】:

你可以这样做:

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

【讨论】:

  • 为值检索编写几个专门的函数是一种有效的方法,但不需要模板类。前三个函数(asByte、asDouble、a​​sBoolean)就足够了。
  • 您可以在通用代码中使用 variant_cast,而使用三个 asXXX-Function 则无法做到这一点。除此之外,我同意仅具有三个功能通常足以解决您的问题。我不得不承认我也认为你的问题是一个挑战;)
【解决方案3】:

为了完整起见,使用 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_BOOLEANSDK_BYTE,现在必须编写variant_cast&lt;mySDK_BOOLEAN&gt;(myVariant)variant_cast&lt;mySDK_BYTE&gt;(myVariant)。谢谢 Xeo。

【讨论】:

    【解决方案4】:

    我相信这可以通过一个未使用的模板参数和一个未使用但唯一的结构声明作为参数来完成:

    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()/*...*/
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-07-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-08-30
      • 2022-01-10
      • 1970-01-01
      相关资源
      最近更新 更多