3

I would like to craft an "interface"/mix-in class (template), and ensure nobody thinks that adding a member to this class template is a good idea, I'd like to static_assert on this condition. Unfortunately, std::is_empty does not allow virtual functions (as polymorphic classes need some space to store their virtual function table pointer or equivalent implementation details).

Is there a variant of std::is_empty that allows for virtual functions, but not data members (or is it easily written)?

I'd like this to work (plus all dark corner cases I can't think of right now):

#include <type_traits>

struct A {};
struct B : A {};
struct AA { virtual ~AA() = default; };
struct BB : AA {};

static_assert(std::is_empty_v<A>);
static_assert(std::is_empty_v<B>);

// These should also work with the replacement for is_empty:
static_assert(std::is_empty_v<AA>);
static_assert(std::is_empty_v<BB>);

int main()
{}

Live demo here.

I'm guessing it requires compiler magic or very platform-specific hacks/checks depending on how exactly virtual functions are implemented.

rubenvb
  • 74,642
  • 33
  • 187
  • 332
  • Can you explain how `std::is_empty::value && std::is_polymorphic::value` doesn't meet your needs? – Peter Nov 15 '19 at 12:28
  • 2
    @Peter a class with virtual methods has `is_empty::value == false` – 463035818_is_not_an_ai Nov 15 '19 at 12:32
  • If you're on a team and you need to enforce coding policies, it might be time to look into a linter: https://github.com/exercism/cpp/issues/8 – parktomatomi Nov 15 '19 at 13:03
  • You could try exploiting *empty base class optimization*: derive a class from it that has one data member, and check the size of the derived class is the size of that data member. – user253751 Nov 15 '19 at 13:33
  • @user253751 That doesn't work due to the virtual function table pointer or other implementation defined details that increase the size of a polymorphic class in an implementation-dependent manner. – rubenvb Nov 15 '19 at 13:46
  • @parktomatomi oh don't get me started. Not going to happen anytime soon unfortunately, not at the scale this type of thing would need. – rubenvb Nov 15 '19 at 13:46
  • `I would like to craft an "interface"/mix-in class (template),` can you show examples of such classes? For pure "interface" classes (i.e. only pure virtual functions) a `std::is_polymorphic_v && sizeof(T) == sizeof(dummy_polymorphic)` is enough IMO – Flamefire Nov 15 '19 at 14:15

1 Answers1

3

I would do this:

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

template <typename T>
inline constexpr bool empty_maybe_polymorphic_v = std::is_empty_v<T> ||
    (std::is_polymorphic_v<T> && sizeof(T) <= sizeof(dummy_polymorphic));

It's the best approach I could think of, but it has some limitations:

  • We assume that the overhead of a class being polymorphic is always the same (normally a pretty safe assumption, except if your class ends up with several vtable pointers due to multiple inheritance). I don't see any way to get rid of this limitation.

  • It breaks if T has duplicate empty bases:

    struct A {};
    struct B : A{};
    struct C : A, B {virtual ~C() {}};
    
    std::cout << sizeof(C) << '\n'; // 16, rather than 8
    
HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207