Search code examples
c++multiple-inheritancediamond-problem

C++ Diamond-like inheritance


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?


Solution

  • 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;
    }