gcc を使用して、10 進数データ型のサポートを含むプログラムをコンパイルすると、最近、次のエラーが発生しました。
error: type transparent class 'std::decimal::decimal32' has base classes
GCC のソース ツリーをざっと見てみると、このエラー メッセージが にあることがわかりますgcc/cp/class.c
。
「型透過クラス」とは?そのようなクラスに「基本クラス」があるとエラーになるのはなぜですか?
で、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
タイプのフィールドが 1 つあるため、 a を受け取る関数は aを受け取ることができ、その逆も可能です。関数の装飾も同じように行われます。このアイデアは、おそらく、このクラスをC 型と互換性のある ABI にすることです。__decfloat32
typedef float __decfloat32 __attribute__((mode(SD)));
__decfloat32
std::decimal::decimal32
_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 の不適合ですか? わかりません...必要な2つの異なる型でオーバーロードされた関数を記述できるはずです。しかし、OTOH、コンパイラ拡張機能を使用せずに型を取得することは不可能であるDecimal32
ため、この型の意味は実装定義です...
私のコメントの 1 つで述べたように、型透過クラスは、整数などのプリミティブ型のラッパークラスです。
演算子のオーバーロードを使用しているため、トランスペアレントと呼ばれます。これにより、ラップするプリミティブ型と同じように動作します。
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バージョンはそれをサポートしていないようですが、ドキュメント(上記のリンクを参照)によると、これにより、と同じ呼び出し規約を使用して関数に透過的に渡すことができint
ます。IntWrapper
int