C++ Diamond-like inheritance

256 views Asked by At

I have a class Channel with two two properties, direction and size which are fixed during construction. Direction can take only one of two values, forward (1) or backward(-1). Size can take any value, but there is a physically meaningful distinction between 0 and any nonzero value.

I'd like to be able to write functions that accept Channel objects with known values for the direction and/or size, and I thought to implement this using derived classes:

                            Channel
                               |
       -----------------------------------------------
       |                |              |             |
ForwardChannel  BackwardChannel  ZeroChannel  NonzeroChannel
       |                |              |             |
       |                ----------------            ...
       |                        |      |
       |          BackwardZeroChannel  |
       |                               |
       ---------------------------------
                        |
               ForwardZeroChannel

Obviously I didn't draw all of the permutations.

I tried implementing it as so

class Channel {
  Channel(int direction, int size) { ... };
  ...
}

class ForwardChannel: public virtual Channel {
  ForwardChannel(int size) : Channel(1, size) { ... }
  ...
}

class ZeroChannel: public virtual Channel {
  ZeroChannel(int direction) : Channel(direction, 0) { ... }
  ...
}

class ForwardZeroChannel: public ForwardChannel, ZeroChannel {
  ForwardZeroChannel() : ForwardChannel(0), ZeroChannel(1)
  ...
}

Instantiating ForwardChannel and ZeroChannel works fine. Instantiating ForwardZeroChannel calls only the default constructor for Channel which doesn't set the values. I have to add Channel(1, 0) to the initializer list:

class ForwardZeroChannel: public ForwardChannel, ZeroChannel {
  ForwardZeroChannel() : Channel(0, 1), ForwardChannel(0), ZeroChannel(1)
  ...
}

but that seems to defeat some of the purpose of deriving from ForwardChannel and ZeroChannel. Is there a better way of doing this?

3

There are 3 answers

2
Jarod42 On BEST ANSWER

what about (following need c++11, but it could be ported to c++99 (except the 'template using') ):

class Channel {
public:
    virtual ~Channel();
protected:
  Channel(int direction, int size);
};

template<bool forward, bool zero>
class ChannelT : public Channel {
public:
    template <bool b = zero, typename T = typename std::enable_if<b>::type>
    ChannelT() : Channel(forward ? 1 : 0, 0) {}

    template <bool b = zero, typename T = typename std::enable_if<!b>::type>
    explicit ChannelT(int size) : Channel(forward ? 1 : 0, size) { assert(size != 0); }
};

template <bool zero> using ForwardChannel = ChannelT<true, zero>;
using ForwardZeroChannel = ChannelT<true, true>;
using ForwardNonZeroChannel = ChannelT<true, false>;
// And so on for the 5 other types...

int main() {
    ForwardZeroChannel forwardZeroChannel;
    ForwardNonZeroChannel forwardNonZeroChannel(42);
    return 0;
}
0
Santosh Sahu On
       class ForwardZeroChannel: public ForwardChannel, ZeroChannel {
           ForwardZeroChannel() : Channel(0, 1), ForwardChannel(0), ZeroChannel(1)
        ...
       }

According to "Herb Shutter" it's the responsibilty of the derived class object to initialise the parent class sub-objects by calling the constructors(in virtual derivation case) otherwise compiler will by call the constructor of the parent sub-objects.

0
Lol4t0 On

Another option will be making Channel an interface with pure virtual size and direction functions and default constructor. Then ForwardChannel or ZeroChannel derive from Channel and implement specific functions.

struct Channel 
{
    virtual int direction() const = 0;
    virtual int size() const = 0;
    virtual ~Channel() {}
};

struct ForwardChannel: virtual public Channel
{
    virtual int direction() const override { return 1; }
};

struct ZeroChannel: virtual public Channel
{
    virtual int size() const override { return 0; }
};

struct ForwardZeroChannel: public ForwardChannel, public ZeroChannel
{

};

int main()
{
    ForwardZeroChannel z;
    return z.size() + z.direction();
}