0

I'm making a base class for my container classes to derive from so I can maintain a consistent interface. It currently looks something like this:

template <typename Datatype>
class BaseClass
{
    public:
    virtual Datatype Foo() = 0;
    virtual Datatype Bar() = 0;
};

template <typename Datatype>
class DerivedClass: public BaseClass<Datatype>
{
    public:

    virtual Datatype Foo()
    {
    }

    virtual Datatype Bar()
    {
    }
};

However, with some of my derived classes, Foo() and Bar() may need to have different return types from each other. Without having a template parameter for each different return type in the base class, how can I give the derived classes some room for changing this sort of thing?

EDIT:

The types the derived classes use are potentially completely different and invariant. Really, the derived classes aren't guaranteed to have any sort of common ground other than the method names.

Maxpm
  • 24,113
  • 33
  • 111
  • 170
  • 3
    "the derived classes aren't guaranteed to have any sort of common ground other than the method names" - how is that a "consistent interface", then? If I have a `BaseClass *p`, how and why would I call `p->Foo()` if some subclasses return `int` and others return `std::string`? You could perhaps throw Boost.Variant or Boost.Any at it, though. – Steve Jessop Apr 15 '11 at 12:18
  • @Steve The idea is that I could make a non-friend, non-member function that doesn't care about the datatype. Say, for example, a function to insert an element into the container. – Maxpm Apr 15 '11 at 12:36
  • 1
    unfortunately the compiler needs to "know" the return type to call a function, even if you don't use the result. This is to allow for calling conventions where the caller provides the stack space to write the result (and pretty much everything does it that way as far as I know). So you can have template code where the return type is irrelevant (because the compiler will figure it out when the template is instantiated, but the programmer doesn't care), but dynamic polymorphism doesn't work that way - a call to a virtual function needs the return type to be the same for all overrides. – Steve Jessop Apr 15 '11 at 12:43
  • ... where when I say "the same type": covariant returns are the one exception, and are hedged with enough requirements that calling conventions can cope. – Steve Jessop Apr 15 '11 at 12:45

6 Answers6

6

Provide a trait which would be specialized in the cases where you need different result.

template <typename Datatype>
struct BaseTraits
{
    typedef Datatype FooResult;
    typedef Datatype BarResult;
};

template <typename Datatype, typename Traits = BaseTraits<Datatype> >
class BaseClass
{
    public:
    virtual Traits::FooResult Foo() = 0;
    virtual Traits::BarResult Bar() = 0;
};
AProgrammer
  • 51,233
  • 8
  • 91
  • 143
3

If you know the number of potential types ahead of time you can extend what you've got by adding additional types to the base class template...

template <typename FOO_TYPE,typename BAR_TYPE>
class BaseClass
{    
public:    
    virtual FOO_TYPE Foo() = 0;    
    virtual BAR_TYPE Bar() = 0;
};

template <typename FOO_TYPE,typename BAR_TYPE>
class DerivedClass: public BaseClass<FOO_TYPE,BAR_TYPE>
{    
public:    
    virtual FOO_TYPE Foo()    {    }    
    virtual BAR_TYPE Bar()    {    }
};

This could get out of hand rapidly if you have more than a few types.

grayDad
  • 324
  • 1
  • 7
1

If the return types are co-variant, they can be changed, or you can write some kind of conversion function and have like, a real_bar or something like that.

Puppy
  • 144,682
  • 38
  • 256
  • 465
  • The types are potentially completely different and not linked in any sort of class hierarchy. For example, one derived class might return an `int` for `Foo()` and a `string` for `Bar()`, while another class returns `vector`s for both, etc. – Maxpm Apr 15 '11 at 12:09
  • @Maxpm : Do you know all the potential derived classes' return types up front? – ildjarn Apr 15 '11 at 12:20
  • @Maxpm: That certainly cannot be done. If you have a `Bar()` that is supposed to return a `string`, how on earth could you call it when it really returns an `int`? You're trying to make a compile-time type vary at run-time. The only run-time types available in C++ is inheritance- if you don't use it, they are fixed at compile-time. – Puppy Apr 15 '11 at 12:36
  • @ildjarn No. The types could be literally anything. The derived methods might even be templated. – Maxpm Apr 15 '11 at 12:38
  • Covariance only applies if the function returns a pointer or reference. These functions return objects. – Mike Seymour Apr 15 '11 at 12:41
  • @Maxpm: How on earth could the compiler compile such a program? There would be no way to make it meaningful. All you can do is use a co-variant return type, such as `boost::variant` or `boost::any` that can hold all possible return types. – Puppy Apr 15 '11 at 12:41
1

traits may help. The C++ Templates - The Complete Guide book provides an example that illustrates this in the chapter entitled Traits and Policy Classes. It has an example that uses an accumulator to return different types.

EDIT: I can see AProgrammer has given an example already

Community
  • 1
  • 1
dubnde
  • 4,359
  • 9
  • 43
  • 63
1

It's NOT possible to return different data types. The only way would have been to make the methods template and that is restricted because virtual methods can not be templates.

iammilind
  • 68,093
  • 33
  • 169
  • 336
1
#include <iostream>
#include <typeinfo>

using namespace std;

template <typename T, typename R = T>
class base
{
  public:
    virtual R foo() 
    {
      cout << "foo(): data type = " << typeid(T).name() << endl; return R();
    }

    virtual R bar()
    {
      cout << "bar(): return type = " << typeid(R).name() << endl; return R();
    }
};

int main()
{
  base<int> b;
  base<int, long> b1;

  b.foo(); b.bar();
  b1.foo(); b1.bar();

  cout << typeid(b).name() << endl;
  cout << typeid(b1).name() << endl;

  return 0;
}

The above program returned the following:

With g++:

$ ./rettypetemplate
foo(): data type = i
bar(): return type = i
foo(): data type = i
bar(): return type = l
4baseIiiE
4baseIilE

With Visual Studio 2005 cl.exe:

C:\Program Files\Microsoft Visual Studio 8\VC>rettypetemplate.exe
foo(): data type = int
bar(): return type = int
foo(): data type = int
bar(): return type = long
class base<int,int>
class base<int,long>

Though this sample shows how to do it, the answer given by AProgrammer shows a good way of handling it using traits. The above example will work well for fundamental data types but not for user defined types. To handle user defined types, traits is the way to go. Refer to either the "The C++ Template" book by Jossutis or "Modern C++ design" to know more about traits.

Community
  • 1
  • 1
yasouser
  • 5,113
  • 2
  • 27
  • 41