46

使用gcc编译一个包含十进制数据类型支持的程序,最近遇到如下错误:

error: type transparent class 'std::decimal::decimal32' has base classes

快速查看 GCC 的源代码树会发现此错误消息位于gcc/cp/class.c.

什么是“类型透明类”?为什么这样的类具有“基类”是错误的?

4

2 回答 2

21

多读一点 GCC 的源代码,在semantics.c

  if (TREE_CODE (t) == RECORD_TYPE
      && !processing_template_decl)
    {
      tree ns = TYPE_CONTEXT (t);
      if (ns && TREE_CODE (ns) == NAMESPACE_DECL
          && DECL_CONTEXT (ns) == std_node
          && DECL_NAME (ns)
          && !strcmp (IDENTIFIER_POINTER (DECL_NAME (ns)), "decimal"))
        {
          const char *n = TYPE_NAME_STRING (t);
          if ((strcmp (n, "decimal32") == 0)
              || (strcmp (n, "decimal64") == 0)
              || (strcmp (n, "decimal128") == 0))
            TYPE_TRANSPARENT_AGGR (t) = 1;
        }
    }

此代码表示在以下情况下将类型标记为透明:

  • 它是一个结构,但不是一个模板;
  • 它在命名空间级别,并且该命名空间是std::decimal.
  • 它被命名decimal32decimal64decimal128

里面有你遇到class.c的错误检查,还有更多。

并在mangle.c

      /* According to the C++ ABI, some library classes are passed the
         same as the scalar type of their single member and use the same
         mangling.  */
      if (TREE_CODE (type) == RECORD_TYPE && TYPE_TRANSPARENT_AGGR (type))
        type = TREE_TYPE (first_field (type));

评论是这里的关键。我认为这意味着将透明类型替换为其第一个(也是唯一一个)成员的类型,因此它可以在第一个成员可以使用的任何地方使用。例如,在我include/decimal的类std::decimal::decimal32中,只有一个类型的字段__decfloat32(来自 previous typedef float __decfloat32 __attribute__((mode(SD)));),因此任何采用 a 的函数都__decfloat32可以采用 a std::decimal::decimal32,反之亦然。甚至功能装饰也是如此。这个想法可能是使此类 ABI 与 C 类型兼容_Decimal32_Decimal64并且_Decimal128.

现在,您如何获得class decimal32基类?我唯一的猜测是你包含了不兼容的(可能是旧的)头文件,具有完全不同的实现。

更新

经过一番调查,看来我对 ABI 和功能修饰的猜测是正确的。以下代码:

#include <decimal/decimal>
using namespace std::decimal;

 //This is a synonym of C99 _Decimal32, but that is not directly available in C++
typedef float Decimal32 __attribute__((mode(SD)));

void foo(decimal32 a) {}
void foo(Decimal32 a) {}

给出了奇怪的错误:

/tmp/ccr61gna.s: Assembler messages:
/tmp/ccr61gna.s:1291: Error: symbol `_Z3fooDf' is already defined

也就是说,编译器前端在重载中没有看到任何问题并发出 asm 代码,但是由于两个函数的修饰相同,因此汇编器失败了。

现在,正如 Ben Voigt 在评论中所建议的那样,这是否与 GCC 不符?我不知道......你应该能够用你想要的任何两种不同类型编写重载函数。但是OTOH,不使用一些编译器扩展是不可能得到Decimal32类型的,所以这种类型的含义是实现定义的......

于 2013-07-17T23:28:25.150 回答
12

正如我的评论之一所提到的,类型透明类是某些原始类型(如整数等)的包装类。

它们之所以被称为透明是因为它们使用了运算符重载,这使得它们的行为就像它们包装的原始类型一样。

IE,要将一个int透明地包装在一个类中,你需要重载=运算符、++运算符等......

显然,GNU 的 libstdc++将此类类用于某些类型。不知道为什么...

关于基类问题,虽然我不是 100% 确定,但这里有一个猜测。

在 C++ 中处理继承时,您通常需要声明虚拟方法,以解决向上转换的问题。

将方法声明为虚拟将告诉编译器为这些方法创建一个虚拟表,以便可以在运行时查看它们。
这当然会增加类的实例大小

对于类型透明的类,这是不可接受的,因为编译器将无法将此类的实例放入寄存器中(即在传递参数时等),这与包装的类型不同,因此该类赢了不再透明。

编辑

我不知道如何在 GCC 中声明这样一个透明类。我能想到的最接近的事情是透明工会

http://gcc.gnu.org/onlinedocs/gcc/Type-Attributes.html

就像是:

class IntWrapper
{
    int _x;

    /* Constructor, operator overloads... */
};

typedef union
{
    int        integerValue;
    IntWrapper integerWrapper;
}
IntUnion __attribute__( ( __transparent_union__ ) );

我的 GCC 版本似乎不支持它,但根据文档(参见上面的链接),这将允许intIntWrapper使用与int.

于 2013-07-17T20:47:35.827 回答