Подтвердить что ты не робот

С++ - Что такое "прозрачный класс типа"?

Используя gcc для компиляции программы, включающей поддержку десятичных типов данных, я недавно обнаружил следующую ошибку:

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

Быстрый просмотр исходного дерева GCC показывает, что это сообщение об ошибке найдено в gcc/cp/class.c.

Что такое "прозрачный класс типа"? Почему это ошибка для такого класса, чтобы иметь "базовые классы"?

4b9b3361

Ответ 1

Чтение исходного кода 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.
  • И он называется decimal32, decimal64 или decimal128.

В 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 (из предыдущего typedef float __decfloat32 __attribute__((mode(SD)));), поэтому любая функция, которая принимает __decfloat32, может принимать std::decimal::decimal32 и наоборот, Даже украшение функции выполняется одинаково. Возможно, идея состоит в том, чтобы эти классы ABI были совместимы с типами C _Decimal32, _Decimal64 и _Decimal128.

Теперь, как вы получаете class decimal32 с базовыми классами? Мое единственное предположение: вы включаете несовместимые (возможно, более старые) файлы заголовков с совершенно другой реализацией.

UPDATE

После некоторого расследования, похоже, что я догадываюсь о том, что 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, но поскольку обе функции декорированы одинаково, ассемблер терпит неудачу.

Теперь, является ли это несоответствием GCC, как предлагает Бен Фойгт в комментариях? Я не знаю... вы должны иметь возможность писать перегруженные функции с любыми двумя разными типами, которые вы хотите. Но OTOH невозможно получить тип decimal32 без использования некоторого расширения компилятора, поэтому значение этого типа определяется реализацией...

Ответ 2

Как упоминалось в одном из моих комментариев, класс тип-прозрачный является классом оболочки для некоторого примитивного типа, например целых чисел и т.д.

Они называются transparent из-за использования перегрузки оператора, что заставляет их действовать так же, как примитивный тип, который они обертывают.

IE, чтобы обернуть int прозрачно в классе, вам нужно будет перегрузить оператор =, оператор ++ и т.д....

По-видимому, GNU libstdС++ использует такие классы для некоторых типов. Не знаете, почему...

О проблеме базового класса, хотя я не уверен на 100%, предположим.

При работе с наследованием на С++ вам часто нужно объявлять методы virtual, чтобы решить проблемы с повышением.

Объявление метода как виртуального подскажет компилятору создать виртуальную таблицу для методов, чтобы они могли быть просмотрены во время выполнения.
Конечно, это будет увеличить размер экземпляра класса.

Для класса прозрачного класса это неприемлемо, так как компилятор не сможет разместить экземпляр такого класса в регистре (т.е. при передаче аргументов и т.д.), в отличие от обернутого типа, поэтому класс больше не будет прозрачным.

Edit

Я не знаю, как объявить такой прозрачный класс в 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, похоже, не поддерживает ее, но в соответствии с документацией (см. ссылку выше) это позволит int или IntWrapper передавать прозрачные функции с использованием того же самого соглашения, что и int.