tags:

views:

119

answers:

4

I have a templated C++ class that exposes a number of methods, e.g

template<int X, int Y>
class MyBuffer {
public:
    MyBuffer<X,Y> method1();
};

Now, I want to expose additional methods to this class if X == Y. I have done this by subclassing MyBuffer,

template<int X>
class MyRegularBuffer : public MyBuffer<X,X> {
public:
    MyRegularBuffer method2();
};

Now, the problem is that I want to be able to do e.g.

MyRegularBuffer<2> buf = ...
MyRegularBuffer<2> otherBuf = buf.method1().method2();

But I am not sure how to accomplish this. I tried to think of copy constructors, conversion operators, etc, but my C++ skills are unfortunately a bit rusty.

EDIT: I should add that creation of these objects is relatively cheap (and also, it won't happen a lot), which means it would be OK to do something like this:

MyRegularBuffer<2> buf = ...
MyRegularBuffer<2> temp = buf.method1(); // Implicit conversion
MyRegularBuffer<2> otherBuf = temp.method2();

The question is then, how can I define the conversion like that. The conversion operator needs to be in MyBuffer, I think, but I want it to be available only if X==Y.

+1  A: 

It's possible to do what you want if method1 and method2 return a reference to *this. Otherwise, you're going to need to either do a conversion, or make method1 virtual.

rlbond
Even if method1() returns this you still can't call method2() from its return value, because method2() is not part of the interface of MyBuffer<X,Y>.
wilhelmtell
Can you please elaborate on the virtual method approach? I am not sure I got that... The problem I'm facing is the contract, the implementation would be the same (the object are identical in memory if X==Y, so I could just do a reinterpretation).
Krumelur
wilhelmtell, yes, exactly what I meant. You were quicker :)
Krumelur
+1  A: 

The trick is to have a MyRegularBuffer::method1 that calls MyBuffer::method1, then a way to convert the resultant MyBuffer<X,X> into a MyRegularBuffer<X>:

template<int X>
class MyRegularBuffer : public MyBuffer<X,X> 
{
public:

  MyRegularBuffer<X>()
  {}

  MyRegularBuffer<X>(MyBuffer<X,X>)
  {
    // copy fields, or whatever
  }

  MyRegularBuffer<X> method2();

  MyRegularBuffer<X> method1()
  {
    MyRegularBuffer<X> ret(MyBuffer<X,X>::method1());
    return(ret);
  }
};
Beta
Thank you. Yes this is a good idea, but in my case it doesn't add anything to @Aaron McDaid's solution, in that I have to implement method1 again (method1 is actually a number of methods).
Krumelur
+3  A: 

You don't need a separate class to represent the special behaviour. Partial specialization allows you to treat some of the MyBuffer <X,Y> cases specially and give them extra methods.

Keep your original declaration of MyBuffer<X,Y> and add this:

template<int Y>
class MyBuffer<Y, Y> {
public:
    MyBuffer<Y,Y> method1();
    MyBuffer<Y,Y> method2();
};

MyBuffer<1,2> m12; m12.method2(); // compile fail, as desired, as it doesn't have such a method because 1 != 2
MyBuffer<2,2> m22; m22.method2(); // compile success

Edit: my final lines weren't very useful after all, as pointed out by Georg in the comments, so I've deleted them.

Aaron McDaid
The only downside is that method1() has to be re-implemented in MyBuffer<Y,Y>, or else the compiler will complain about an unknown method when you try to call MyBuffer<Y,Y>::method1(). AFAIK, there is no way to have MyBuffer<Y,Y>::method1() delegate its implementation to MyBuffer<X,Y>::method1() without specifying different template parameters where X != Y.
Remy Lebeau - TeamB
Deriving from `MyBuffer` wouldn't work - it doesn't know of the derived class and thus can't return the appropriate type from `method1()`.
Georg Fritzsche
@Georg, I do not understand. Can you be more specific about which line will fail? I have compiled my code and it works. But I must admit I copied the very last line incorrectly about "class MyRegularBuffer". Will update now.
Aaron McDaid
@Remy, that is interesting and I've been struggling with that. If anybody has ideas, let us know. Copying and pasting, or otherwise, the code for method1() would certainly be inconvenient.
Aaron McDaid
I'm addressing your *convenience* snippet: `method1()` is implemented in `MyBuffer<int,int>`, which doesn't know about the classes deriving from it. Ergo code like `MyRegularBuffer<1> a, b; a = b.method1();` could not work - you'd have to use CRTP to give the base class sufficient information (i've shown how in my answer).
Georg Fritzsche
Ah yes. A typedef that could be templated would be handy, or something else to give a convenient name .I just tried to downvote my own answer. Yours is better. But I'm not allowed to (down)vote my own answer.Here's an (ugly) hack that should work: "template<int X> struct MyRegular { typedef MyBuffer<X,X> Buffer; }; " Which could then be used as "MyRegular<2>::Buffer m2;"
Aaron McDaid
I guess this is the best solution, although I can't really understand why I couldn't add methods like that without reimplementing the whole class. But I guess the compiler struggles enough as it is :)
Krumelur
@Aaron: factoring the shared methods in a base class MyBufferBase<X,Y>, of which both MyBuffer<X,Y> and the specialisation MyBuffer<Y,Y> derive, should do the trick. (MyBuffer<X,Y> won't contain any methods as all are defined in MyBufferBase)
Sjoerd
I do not know if using template specialization to define MyBuffer<Y,Y> would allow for derivation, but I do agree that MyBuffer<X,Y>::method1()'s code could be refactored out, such as into a standalone function that MyBuffer<X,Y>::method1() and MyBuffer<Y,Y>::method1() can both call.
Remy Lebeau - TeamB
+3  A: 

I'd go for CRTP here:

template<int X, int Y, class Derived>
struct MyBufferBase {
    // common interface:
    Derived& method1() { return *static_cast<Derived*>(this); }
};

template<int X, int Y>
struct MyBuffer : MyBufferBase<X, Y, MyBuffer<X,Y> > {
    // basic version
};

template<int X> 
struct MyRegularBuffer : MyBufferBase<X, X, MyRegularBuffer<X> > {
    // extended interface:
    MyRegularBuffer& method2() { return *this; }
};
Georg Fritzsche
+1 Thanks for reminding me of that.
Aaron McDaid
Thanks for that. That is absolutely a good idea.
Krumelur
I would have marked as answer if I could have given the mark to more than one answer, but right now Aaron McDaid's solution actually falls out quite well, since my methods are quite thin (it is a wrapper class).
Krumelur