7
class Base1 {
    int x;
};
class Base2 {
   int y;
};
class Derive : public Base1, public Base2 {
public:
    enum {
        PTR_OFFSET = ((int) (Base2*)(Derive*)1) - 1,
    };
};

しかし、コンパイラは文句を言います

期待される定数式

式の値が 4 であることはコンパイラを除いて誰もが知っていますが、何が問題なのですか?

では、コンパイル時にオフセットを取得するにはどうすればよいでしょうか。

4

3 に答える 3

5

Addressing the immediate compiler error you are seeing in the supplied code, (Base2*)(Derive*)1 will most likely become reinterpret_casts when compiled, and that as DyP wrote as a comment to the question is not a constant expression which is required for enumeration initialization. Some compilers, notably GCC are not as strict on this point and will allow for reinterpret_cast in constant expressions even though it is forbidden by the standard (for further discussion of this see the comments for this GCC bug http://gcc.gnu.org/bugzilla/show_bug.cgi?id=49171 and Constexpr pointer value).

The broader question of identifying at compile time what the layout of an object is and the offsets to its various members is a tricky one without a well-defined answer. The standard give implementers a lot of latitude to pad/pack an object's fields, usually out of alignment considerations (for a good summary see http://www.altdevblogaday.com/2013/05/03/cc-low-level-curriculum-part-11-inheritance/). While the relative ordering of an object's field must be maintained, int y in an instance of Derived need not be at an offset of sizeof(x) from the start of the instance of Derived; the answer is compiler and target architecture dependent.

All of that being said, this sort of structure layout information is determined at compile time and at least on some compilers is made accessible (even if not in portable, standards compliant ways). In the answer to this question, C++ Compile-Time offsetof inside a template, Jesse Good provides some code that on GCC at least will allow one to determine field offsets within a type at compile time. This code will unfortunately not provide the correct offsets for base class members.

A good solution to your problem awaits implementation of compile time reflection support in C++, something for which there is ongoing work as part of a standards working group: https://groups.google.com/a/isocpp.org/forum/#!forum/reflection .

于 2014-01-02T02:50:44.120 に答える
2

Clang で動作する例を次に示します。

このアプローチでは、Clang と GCC の両方で利用できるビルトインを使用します。Clang は確認済みですが (以下のコード エクスプローラーのリンクを参照)、GCC は試していません。

#include <iostream>

/* Byte offsets are numbered here without accounting for padding (will not be correct). */
struct A { uint64_t byte_0, byte_8; uint32_t byte_16; };
struct B { uint16_t byte_20, byte_24; uint8_t byte28; uint64_t byte_29; };
struct C { uint32_t byte_37; uint8_t byte_41; };
struct D { uint64_t byte_42; };
struct E : A, B, C, D {};

template<typename Type, typename Base> constexpr const uintmax_t
offsetByStaticCast() {
  constexpr const Type* Type_this = __builtin_constant_p( reinterpret_cast<const Type*>(0x1) )
                                  ? reinterpret_cast<const Type*>(0x1)
                                  : reinterpret_cast<const Type*>(0x1);

  constexpr const Base* Base_this = static_cast<const Base*>( Type_this );

  constexpr const uint8_t* Type_this_bytes = __builtin_constant_p( reinterpret_cast<const uint8_t*>(Type_this) )
                                           ? reinterpret_cast<const uint8_t*>(Type_this)
                                           : reinterpret_cast<const uint8_t*>(Type_this);

  constexpr const uint8_t* Base_this_bytes = __builtin_constant_p( reinterpret_cast<const uint8_t*>(Base_this) )
                                           ? reinterpret_cast<const uint8_t*>(Base_this)
                                           : reinterpret_cast<const uint8_t*>(Base_this);

  constexpr const uintmax_t Base_offset = Base_this_bytes - Type_this_bytes;
  
  return Base_offset;
}

int main()
{
  std::cout << "Size of A: " << sizeof(A) << std::endl;
  std::cout << "Size of B: " << sizeof(B) << std::endl;
  std::cout << "Size of C: " << sizeof(C) << std::endl;
  std::cout << "Size of D: " << sizeof(D) << std::endl;
  std::cout << "Size of E: " << sizeof(E) << std::endl;

  /* Actual byte offsets account for padding. */
  std::cout << "A offset via offsetByStaticCast<E, A>(): " << offsetByStaticCast<E, A>() << std::endl;
  std::cout << "B offset via offsetByStaticCast<E, B>(): " << offsetByStaticCast<E, B>() << std::endl;
  std::cout << "C offset via offsetByStaticCast<E, C>(): " << offsetByStaticCast<E, C>() << std::endl;
  std::cout << "D offset via offsetByStaticCast<E, D>(): " << offsetByStaticCast<E, D>() << std::endl;

  return 0;
}

出力:

Size of A: 24
Size of B: 16
Size of C: 8
Size of D: 8
Size of E: 56
A offset via offsetByStaticCast<E, A>(): 0
B offset via offsetByStaticCast<E, B>(): 24
C offset via offsetByStaticCast<E, C>(): 40
D offset via offsetByStaticCast<E, D>(): 48
Program ended with exit code: 0

コンパイラ エクスプローラで利用可能なコード: https://godbolt.org/z/Gfe6YK

constexprからの有益なコメントと再解釈キャストによる static const void ポインターの初期化に基づいて、どのコンパイラーが正しいですか? 、特に対応する LLVM コミットへのリンクを含むhttp://lists.llvm.org/pipermail/cfe-commits/Week-of-Mon-20120130/052477.html

于 2020-08-26T07:31:54.217 に答える
0

((int) (Base2*)(Derive*)1) - 1最近、コードが壊れていることがわかりましたDerive : public virtual Base2。つまり、仮想ベースのオフセットはコンパイル時には不明です。そのため、C++ 標準では禁止されています。

于 2014-01-03T04:08:30.890 に答える