8

If I have a class Base, with at least one virtual function, and a class Derived which inherits singly from this then (uintptr_t)derived - (uintptr_t)static_cast<Base*>(derived) is guaranteed (by the Itanium ABI) to be zero, even though Derived is not standard layout. However in the general case this is not necessarily true (eg. multiple inheritance).

Is it possible to write a trait which can be used to detect if one class is the primary base class of another?

Useful sections from the Itanium ABI:

http://refspecs.linux-foundation.org/cxxabi-1.83.html

Primary base class

For a dynamic class, the unique base class (if any) with which it shares the virtual pointer at offset 0. It is the first (in direct base class order) non-virtual dynamic base class, if one exists.

Dynamic class

A class requiring a virtual table pointer (because it or its bases have one or more virtual member functions or virtual base classes).

curiousguy
  • 8,038
  • 2
  • 40
  • 58
jleahy
  • 16,149
  • 6
  • 47
  • 66
  • 4
    Your test expression does not at all match the quoted definition. You're looking at the address of the base subobject, while the ABI is discussing order of entries inside the v-table. – Ben Voigt Feb 28 '13 at 22:27
  • @BenVoigt: Nicely spotted. We should really have a link to the ABI. – Kerrek SB Feb 28 '13 at 22:47
  • 1
    I'm extremely curious what use this information could be put to. – Edward Strange Feb 28 '13 at 23:07
  • @BenVoigt I was looking at section 2.4, I was under the impression that the virtual table layout was all contained in section 2.5? – jleahy Mar 01 '13 at 10:21
  • @CrazyEddie I'm trying to create something that's useful, but very illegal C++. It's effectively a boost::variant, but for an unbounded set of derived classes (given that it's not too large/aligned). The derived type is emplaced with a placement move construction, then later the base type is extracted and its virtual destructor is called. Adding a pointer adjustment value would remove this requirement, but I didn't want to add that. This should be part of a long list of static_asserts. – jleahy Mar 01 '13 at 10:23
  • What do you mean by primary? Primary in a given base subobject? Primary in an object of a given dynamic type? – curiousguy Jul 11 '18 at 01:06
  • @jleahy "_The derived type is emplaced_" so the type is the complete type of the object? You should add that info to the question. – curiousguy Jul 11 '18 at 02:06
  • @BenVoigt "_the ABI is discussing order of entries inside the v-table_" reusing the vtable entries that way implies the address of the object is the same – curiousguy Nov 28 '18 at 02:50

2 Answers2

8

This will be part of the next standard This was part of the aborted TR2 via the std::bases and std::direct_bases traits. If you happen to be working with a compiler that includes the draft-TR2, you might have support for this. For example in GCC 4.7.2:

#include <demangle.hpp>
#include <iostream>
#include <tr2/type_traits>

struct T1 { };
struct T2 { };
struct Foo : T1, T2 { };


int main()
{
    std::cout << demangle<std::tr2::direct_bases<Foo>::type>() << std::endl;
}

This prints:

std::tr2::__reflection_typelist<T1, T2>

(The demangler is my own; you may have seen it elsewhere.)

I trust you can build a suitable "is polymorphic and has precisely zero or one bases" trait yourself.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • Awesome! I've wanted something like this for ages, though now I can't remember what I even originally wanted it for. – GManNickG Feb 28 '13 at 23:44
  • 1
    "Primary base" in the Itanium ABI is distinct from "direct base" in the C++ standard. It's possible to have multiple direct base classes but only one base can be primary. – Praxeolitic Dec 29 '14 at 01:35
0

Below is a wild, not thoroughly tested attempt at doing something helpful with C++11 only (actually, it does not really require any C++11 feature, but it's easier to write this way).

However, this trait only checks the transitive closure of the "is primary base class of" property: I could not figure out a non-intrusive way of verifying whether a class is a direct base class of another class.

#include <type_traits>

template<typename B, typename D, D* p = nullptr, typename = void>
struct is_primary_base_of : std::false_type { };

template<typename B, typename D, D* p>
struct is_primary_base_of<B, D, p,
    typename std::enable_if<
        ((int)(p + 1024) - (int)static_cast<B*>(p + 1024)) == 0
        >::type
    >
    :
    std::true_type { };

Here is an example:

struct A { virtual ~A() { } };

struct B : A { };

struct C { virtual ~C() { } };

struct D : B, C { };

struct E : virtual A, C { };

int main()
{
    // Does not fire (A is PBC of B, which is PBC of D)
    static_assert(is_primary_base_of<A, D>::value, "Error!");

    // Does not fire (B is PBC of C)
    static_assert(is_primary_base_of<B, D>::value, "Error!");

    // Fires (C is not PBC of D)
    static_assert(is_primary_base_of<C, D>::value, "Error!");

    // Fires (A is inherited virtually by E, so it is not PBC of E)
    static_assert(is_primary_base_of<A, E>::value, "Error!");

    // Does not fire (C is the first non-virtual base class of E)
    static_assert(is_primary_base_of<C, E>::value, "Error!");
}
Andy Prowl
  • 124,023
  • 23
  • 387
  • 451
  • This isn't testing what the ABI is talking about (understandable, because the question also got it wrong). – Ben Voigt Feb 28 '13 at 22:30
  • @BenVoigt: OK, maybe if you could clarify what I should test, I can try to change the answer. Otherwise, I will just delete it – Andy Prowl Feb 28 '13 at 22:35
  • It's a helpful answer that shouldn't be deleted. But you might want to discuss instance layout vs v-table layout. I don't know any way to check v-table layout without invoking undefined behavior (e.g. casting pointer-to-member-functions to integral types). – Ben Voigt Feb 28 '13 at 22:38
  • I do think your answer would benefit from test case(s) that include non-polymorphic base classes at various places in the base list. – Ben Voigt Feb 28 '13 at 22:39
  • @BenVoigt: OK, I'll keep it there then, maybe the OP can get some benefit. Regarding the test cases, yes, this hasn't be thoroughly tested, but now I'm no more sure *what* I should test. – Andy Prowl Feb 28 '13 at 22:46
  • I have no idea what you code means. Can you explain `(int)(p + 1024)`? What is `p`? – curiousguy Jul 11 '18 at 02:33
  • I can't get this code to work for even the simplest example on any compiler I tried: https://godbolt.org/z/QIS8i0 - I looked at ICC, GCC, Clang, all of them have `is_primary_base` always returning false. What am I missing? – John Zwinck Sep 25 '18 at 03:07
  • You can’t use `reinterpret_cast` (or a C-style cast that performs it) in a constant expression, although compilers are not always rigorous about enforcing it. – Davis Herring Dec 04 '19 at 02:35