Home > Back-end >  Can I make error when diamond inheritance with template?
Can I make error when diamond inheritance with template?

Time:12-20

I want to cause an error when inheritance is duplicated. Here is how I found it.

#include <utility>

class Person {};

class Man       : public Person {};
class Woman     : public Person {};

template <typename... Types>
class merge_class : public Types... {};

template <typename... Types>
struct condition
{
    using merge = merge_class<Types...>;
    
    using type = std::enable_if<
        std::is_convertible<merge, Person>::value // condition
        , merge>::type;
};

class BummooKim : public condition<Man>::type {};
class Daniel : public condition<Woman>::type {};
//class Unkown : public condition<Man, Woman>::type {}; // There is an error in the declaration.

However, I found that this way cannot be used if there is a non-default constructor.

I was wondering if there is a keyword to indicate that it must be single-inherited.

If c doesn`t support 'keyword', I want another way.

example

class OtherWay : public condition<Man, Other>::type 
{
    OtherWay() : Man() {}
};

CodePudding user response:

What you are running into is called diamond inheritance (https://www.makeuseof.com/what-is-diamond-problem-in-cpp/) and IMO is best avoided except for "interfaces". I prefer using composition of implementations also known as the mixin pattern (which in turn uses CRTP, the curiously recursing template pattern). In this pattern you make implementations for seperate capabilities (e.g. in the following examples a man can shout and a woman can smile and all persons can say hi.).

Detecting multiple inheritance at compile time is not possible (a feature in c that might have enabled it never made it through the standard committee).

I show a way to at least detect, at compile time, that a person cannot be a man and a woman at the same time (at least in this example).

In MSVC this program will give the following compilation error :
Error C2338 Being both a Man and a Woman, is not correct in this program

#include <iostream>
#include <string>

//-----------------------------------------------------------------------------
// multiple inheritance from interfaces (abstract base classes is fine)

// class to introduce a concept of an interface 
// and set all the constructors/destructors default behavior.

class Interface
{
public:
    virtual ~Interface() = default;

protected:
    Interface() = default;                      // protected constructor, avoids accidental instantiation
};

//-----------------------------------------------------------------------------
// for each aspect/capability of a person define a seperate interface
// this will also allow client code to cast an object to either of those
// interfaces to check if functionality is available

class PersonItf :
    public Interface
{
public:
    virtual void SayHi() const = 0;
};

//-----------------------------------------------------------------------------
// A man can shout

class ManItf :
    public Interface
{
public:
    virtual void Shout() const = 0;
};

//-----------------------------------------------------------------------------
// A woman can smile

class WomanItf :
    public Interface
{
public:
    virtual void Smile() const = 0;
};

//-----------------------------------------------------------------------------
// mixin classes for reusable code

template<typename base_t>
class PersonImpl :
    public PersonItf
{
public:
    void SayHi() const override
    {
        std::cout << "Hi!\n";
    }
};

template<typename base_t>
class ManImpl :
    public ManItf
{
public:
    void Shout() const override
    {
        std::cout << "Yohoohoooo!\n";
    };
};

template<typename base_t>
class WomanImpl:
    public WomanItf
{
public:
    void Smile() const override
    {
        std::cout << "Smile!\n";
    };
};


//-----------------------------------------------------------------------------
// now we can group capabilities together in classes
// 

class Man :
    public ManImpl<Man>
{
};

class Woman :
    public WomanImpl<Woman>
{
};

class ManAndWoman :
    public ManImpl<ManAndWoman>,
    public WomanImpl<ManAndWoman>
{
};

//-----------------------------------------------------------------------------
// this Person class will check validity of the composition
// at compile time.

template<typename type_t>
struct Person :
    public PersonImpl<type_t>,
    public type_t
{
    static_assert(!(std::is_base_of_v<WomanItf, type_t>&& std::is_base_of_v<ManItf, type_t>), "Being both a Man and a Woman is not correct in this program\n");
};

//-----------------------------------------------------------------------------

class Daniel : public Person<ManAndWoman> {};
class Santa : public Person<Man> {};

int main()
{
    Daniel daniel;
    Santa santa;

    daniel.SayHi();
    santa.Shout();

    return 0;
}

CodePudding user response:

Define

#include <utility>

namespace Definer
{
    // define
    namespace Classification
    {
        class Person { unsigned int age; };
    }
    class Man       : public Classification::Person {};
    class Woman     : public Classification::Person {};

    // conditions
    template <typename Derived>
    static constexpr bool is_convertible_person()
    {
        constexpr bool result = std::is_convertible<Derived, Classification::Person>::value;
        static_assert(result, "Person is duplicated.");
        return result;
    }

    template <typename Derived>
    concept Condition
        = is_convertible_person<Derived>();
}

How to make it possible through inheritance

namespace Definer
{
    template <typename Derived>
        requires Condition<Derived>
    class Requires
    {};
}

class Daniel :
    public Definer::Man,
    public Definer::Requires<Daniel> // Not require, but similar
{};

Comparing the class sizes. And make error.

class Compare :
    public Definer::Man
{};
constexpr size_t size_of_Daniel = sizeof(Daniel);   // 4U
constexpr size_t size_of_Compare = sizeof(Compare); // 4U

// This class make an error well, except for repeated occurrences.
class Santa :
    public Definer::Man,
    public Definer::Woman,
    public Definer::Requires<Santa> // Not require, but similar
{};

//class Desire :
//  public Definer::Man
//  requires Definer::Condition<Desire> // It is not support now. I don't know if it will be possible later. 
                                        // 2021.12.20 c  20 preview
//{};
  • Related