C++ - What is a "type transparent class"?

Using gcc to compile a program that includes support for decimal data types, I recently encountered the following error:

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

A quick look at GCC's source tree shows that this error message is found in gcc/cp/class.c.

What is a "type transparent class"? Why is it an error for such a class to have "base classes"?


Solution 1:

Reading the source code of GCC a bit more, in 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;
        }
    }

This code means that a type is marked transparent if:

  • It is a struct, but not a template;
  • And it is at namespace level, and that namespace is std::decimal.
  • And it is named decimal32, decimal64 or decimal128.

In class.c there is the error check you encountered, and a few more.

And in 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));

The comment is key here. I think it means that a transparent type is replaced for the type of its fist (and only) member, so it can be used anywhere it first member can. For example, in my include/decimal the class std::decimal::decimal32 has a single field of type __decfloat32 (from a previous typedef float __decfloat32 __attribute__((mode(SD)));), so any function that takes a __decfloat32 can take a std::decimal::decimal32 and vice-versa. Even the function decoration is done the same. The idea is probably to make this classes ABI compatible with the C types _Decimal32, _Decimal64 and _Decimal128.

Now, how are you getting a class decimal32 with base classes? My only guess is that you are including incompatible (maybe older) header files, with a totally different implementation.

UPDATE

After some investigation, it looks like my guess about the ABI and function decoration is right. The following code:

#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) {}

gives the curious error:

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

That is, the compiler front-end sees no problem in the overload and emits the asm code, but since both functions are decorated the same the assembler fails.

Now, is this a non-conformance of GCC, as Ben Voigt suggests in the comments? I don't know... you should be able to write overloaded functions with any two different types you want. But OTOH, it is impossible to get the Decimal32 type without using some compiler extension, so the meaning of this type is implementation defined...

Solution 2:

As mentioned in one of my comment, a type-transparent class is a wrapper class to some primitive type, like integers, etc.

They are called transparent because of their use of operator overloading, which makes them act just like the primitive type they wrap.

IE, to wrap an int transparently in a class, you'll need to overload the = operator, the ++operator, etc...

Apparently, GNU's libstdc++ uses such classes for some types. Not sure why...

About the base class issue, while I'm not 100% sure, here's a guess.

When dealing with inheritance in C++, you'll often need to declare virtual methods, to resolve issues with upcasting.

Declaring a method as virtual will tell the compiler to create a virtual table for the methods, so they can be looked at runtime.
This will of course increase the instance size of the class.

For a type-transparent class, this is not acceptable, as the compiler won't be able to place an instance of such a class in a register (i.e when passing arguments, etc), unlike the wrapped type, and so the class won't be transparent anymore.

Edit

I've no idea how to declare such a transparent-class in GCC. The closest thing I can think of is transparent unions:

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

Something like:

class IntWrapper
{
    int _x;

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

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

My GCC version does not seem to support it, but according to the documentation (see above link), this would allow int or IntWrapper to be passed to functions transparently using the same calling convention as int.