4

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?

  • 1
    Its not defeating. Its the very point of a shared virtual public base. The inner derivations are not responsible for the base initialization, the point-of-convergence down the derivation tree (in your case `ForwardZeroChannel`) *is* responsible for it. – WhozCraig Sep 07 '13 at 18:29
  • 3
    With non-virtual inheritance a derived class is responsible for initializing its immediate base class. With virtual inheritance the most derived class is responsible for initialization of the virtual base class. If this weren't the case it would produce a conflict: In your example, should `ForwardChannel` or `ZeroChannel` initialize `Channel` in your `ForwardZeroChannel` class? – IInspectable Sep 07 '13 at 18:30
  • If you follow the conventional wisdom of making non-leaf classes abstract, you can remove the `Channel` constructor calls in the intermediate classes entirely. – Kerrek SB Sep 07 '13 at 18:40
  • @WhozCraig I still think it's defeating in that I could call the `Channel` constructor from the `ForwardZeroChannel` initializer list with values that are invalid for a `ForwardChannel` or `ZeroChannel`. What I was hoping for (among other things) was a way to use the inheritance to enforce valid values. – user2757478 Sep 07 '13 at 19:05
  • I would say, in your configuration, Channel should not have direction/size parameters, just virtual pure getters which where implemented only for ForwardChannel and ZeroChannel (and their opposites). – Jarod42 Sep 07 '13 at 19:19
  • By the way, you only have 4 distinct concrete classes, so it may be overcomplicated to create 5 distinct abstract classes to manage them. – Jarod42 Sep 07 '13 at 19:25

3 Answers3

1
       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.

Santosh Sahu
  • 2,134
  • 6
  • 27
  • 51
1

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;
}
Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • I think this works. It's a little beyond my template experience. And I'm using an old version of g++ that doesn't support template aliases, so I can't confirm everything. If not, I might have to follow your pure virtual getters suggestion. – user2757478 Sep 07 '13 at 20:47
  • The main problem of this approach is that, for exmaple, `ForwardChannel ` _is not a type_. So `ForwardChannel* ch = channels.get();` does not work. – Lol4t0 Sep 07 '13 at 22:33
1

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();
}
Lol4t0
  • 12,444
  • 4
  • 29
  • 65