Question

We have a special framework for interfaces in our project, and part of the requirements is that classes which represent an interface may only be used as virtual base classes, not as non-virtual ones. Is there a way to enforce this in code? That is, produce a compilation error if the class is derived from non-virtually.

I have access to C++11 as implemented by VS 2010: this means static_assert, enable_if and <type_traits> are available.

Was it helpful?

Solution

IMO, there is no clean and platform independent solution available to this problem.

The best way is to manually go and change each and every inheritance to virtual inheritance.
To accomplish that, identifying the derived classes of your interface (say class Base) is easy(!). Below steps can be followed for that:

  1. Make class Base as final (c++11); i.e. class Base final { ...
  2. Compile the code, it will generate compiler error for all its derived classes
  3. Go and check every derived class and make the inheritance as virtual
  4. Remove the final keyword and compile the code successfully

This process (unfortunately) has to be followed periodically, whenever you want to do such sanity checking.

OTHER TIPS

This is possible to check at compile time. The key is that, if we have a diamond pattern:

diamond

You can unambiguously cast D& to A&. However, if the inheritance is non-virtual:

not diamond

the cast would ambiguous. So let's try to make a diamond!

template <typename Base, typename Derived>
class make_diamond {
    struct D2 : virtual Base { }; // this one MUST be virtual
                                  // otherwise we'd NEVER have a diamond
public:
    struct type : Derived, D2 { };
};

At which point it's just another void_t-style type trait:

template <typename Base, typename Derived, typename = void> 
struct is_virtual_base_of : std::false_type { };

template <typename Base, typename Derived>
struct is_virtual_base_of<Base, Derived, void_t<
    decltype(static_cast<Base&>(
        std::declval<typename make_diamond<Base, Derived>::type&>()))
    >> : std::true_type { };

If the cast is unambiguous, the expression in the partial specialization will be valid, and that specialization will be preferred. If the cast is ambiguous, we'll have a substitution failure, and end up with the primary. Note that Base here doesn't actually need to have any virtual member functions:

struct A { };
struct B : public A { };
struct C : virtual  A { };

std::cout << is_virtual_base_of<A, B>::value << std::endl; // 0
std::cout << is_virtual_base_of<A, C>::value << std::endl; // 1

And if it has any pure virtual member functions, we don't have to override them since we're never actually constructing an object.

struct A2 { virtual void foo() = 0; };
struct B2 : public A2 { void foo() override { } };
struct C2 : virtual A2 { void foo() override { } };

std::cout << is_virtual_base_of<A2, B2>::value << std::endl; // 0
std::cout << is_virtual_base_of<A2, C2>::value << std::endl; // 1

Of course if your class is marked final, this won't work at all. But then, if it were final, it wouldn't matter what kind of inheritance it had anyway.

Interesting problem. You may be able to get close to what you want by hiding the interface class and exposing a concrete class that inherits from the interface virtually. This obviously entails some workarounds and awkwardness, but it might be adaptable to your needs. Here's an example:

#include <iostream>
using namespace std;

class Hide {
    struct VInterface {
        void foo() const { cout << "VInterface::foo()\n"; }
        VInterface const &as_interface() const { return *this; }
    protected:
        virtual ~VInterface() { }
    };
public:
    struct VBase : virtual VInterface {
    };
};
typedef Hide::VBase VBase;
struct VDiamond1 : VBase { };
struct VDiamond2 : VBase { };
struct VConcrete : VDiamond1, VDiamond2 { };

int main() {
    VConcrete vc;
    auto const &vi = vc.as_interface();
    vi.foo();
}

It may be possible to reconstruct a name using decltype() and as_interface() that may be usable for inheritance, but the ones I tried resulted in compiler errors that the destructor was protected, so I expect that if it is possible, it's at least relatively difficult and might be sufficient for your needs.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top