简体   繁体   中英

C++ - detect is first base class at compile time

I'd like to detect that class Base is the first base of class Deriv . That is, they have the same pointer.

The example below doesn't work. I tried a few more things, wrapping casts in functions and unions, and got nowhere.

With a union it works only if all the types are literal - default destructable etc, which my classes are not.

Is there a way to do it? ideally in C++14?

template <class Base, class Deriv, bool IsBase = std::is_base_of<Base, Deriv>::value>
struct is_first_base_of {
    static constexpr bool value = reinterpret_cast<Base*>(1) == static_cast<Base*>(reinterpret_cast<Deriv*>(1)); // this doesn't work
};
template <class Base, class Deriv>
struct is_first_base_of<Base, Deriv, false> {
    static constexpr bool value = false;
};

struct A1 { int a1; }
struct A2 { int a2; }
struct B : A1, A2 { int b; }
static_assert(is_first_base_of<A1, B>::value == true, "");
static_assert(is_first_base_of<A2, B>::value == false, "");

UPDATE

That's the code I use now following @user17732522's idea of static_cast-ing to void* . It works on g++ 5.5, but not on 10.3:

template <class Base, class Deriv, bool IsBase = std::is_base_of<Base, Deriv>::value>
struct is_first_base_of {
    static constexpr const Deriv* p0 = nullptr;
    static constexpr const Deriv* p1 = &(p0[123]); // must use non-null!
    static constexpr const void*  base()  { return static_cast<const void*>(static_cast<const Base*>(p1)); }
    static constexpr const void*  deriv() { return static_cast<const void*>(p1); }
    static constexpr bool         value = base() == deriv();
};
template <class Base, class Deriv>
struct is_first_base_of<Base, Deriv, false> {
    static constexpr bool value = false;
};

For aggregate classes you can probably use the aggregate initialization mechanism and conversion operator templates to detect the first base's type.

Except for this class, I don't think it is generally possible to detect the first base class.


If you want to test instead whether the base has the same address, then static_cast<void*>(static_cast<Base*>(x)) == static_cast<void*>(x) should be fine and should also work in constant expression evaluation. It will fail to compile if Base is an ambiguous or inaccessible base of Deriv .

However you need to create a x of type Deriv first, limiting the approach. Something like reinterpret_cast<Deriv*>(/*number*/) to create such an object, as you are attempting in your question, has undefined behavior when passed to the static_cast<Base*> , even if number is 0 . std::declval is also not possible since this is an evaluated context. Therefore Deriv must have some constructor that is known to be usable (in the constant expression).

This is not the same as finding the first base though. The standard specifies memory layout requirements only for standard-layout types, which the type in your question is not.

Even with usual ABI specifications providing for class layout in all cases, this is not the same as finding the first base. Because of empty base class optimization a second base may satisfy this test as well.


Although the latter test verifies that the address of the base class subobject is equal to that of the derived object, the standard does not allow simply reinterpret_cast ing between the two types if the class is not standard-layout, at least since C++17 (and maybe earlier as well). The requirement for that is stricter and called pointer-interconvertibility . This stricter property can be tested for with std::is_pointer_interconvertible_base_of .

Casting from derived-to-base, assuming the addresses are equal, is still possible by applying std::launder after reinterpret_cast . However, the reverse is not allowed by the std::launder precondition.

I am not sure why the std::launder reachability precondition is that way in this case, since it is possible to obtain a pointer to the derived class via static_cast anyway, but the condition doesn't take that into account. In the case of members rather than bases, the condition allows a compiler to assume that other class subobjects cannot be reached through a pointer to such a member, assuming pointer-interconvertibility does not apply.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM