Guillaume Algis
Guillaume Algis

Reputation: 11016

Generic base class with multiple template specialized derived classes

I have a finite amount of classes with the nearly-same implementation, the only different being the underlying type of data they manipulate:

class IntContainer
{
public:
    void setData(int data);
    int getData();
    int _data;
};

class BoolContainer
{
public:
    void setData(bool data);
    bool getData();
    bool _data;
};

class StringContainer
{
public:
    void setData(std::string data);
    std::string getData();
    std::string _data;
};

// Etc. You get the idea.

I'd like to reduce the code duplication of these classes by using templates like so:

template<typename T>
class GenericContainer
{
public:
    void setData(T data);
    T getData();
    T _data;
};

And specialization:

typedef GenericContainer<int> IntContainer;
typedef GenericContainer<bool> BoolContainer;
typedef GenericContainer<std::string> StringContainer;

This works well. But I'd also like to add an abstract base class to these specialized classes to be able to manipulate them in a generic way (eg. in a collection). The problem is this base class should have the getData and setData methods to be able to call them even without knowing the dynamic type of the object manipulated.

I would implement it with something like this:

class Base
{
public:
    virtual void setData(??? data) = 0;
    virtual ??? getData() = 0;
};

// Modify GenericContainer's definition like so
template<typename T>
class GenericContainer : Base { ... }

And use it somehow like that:

int main(int argc, char const *argv[])
{
    IntContainer intc = IntContainer();
    intc.setData(42);
    std::cout << intc.getData() << std::endl;

    BoolContainer boolc = BoolContainer();
    boolc.setData(false);
    std::cout << boolc.getData() << std::endl;

    std::vector<Base> v;
    v.push_back(intf);
    v.push_back(boolf);

    for (std::vector<Base>::iterator it = v.begin() ; it != v.end(); ++it)
        std::cout << it->getData() << std::endl;

    return 0;
}

The problem is I don't know how to write the Base methods prototypes as the type is unknow (and does not matter, the derived class implementation should be called at runtime based on the dynamic type of the object).

TL;DR: How to implement an abstract base class over several fully specialized templated classes ?

Upvotes: 8

Views: 6929

Answers (4)

Glenn Teitelbaum
Glenn Teitelbaum

Reputation: 10343

Assuming you have some design flexibility, you can change your interface to accommodate this, although its not as efficient as an infinite virtual table

You can set values through construction, or >>

You can get values through <<

Your vector needs to be a base pointer or reference, the size of each base object is variable, the pointer, explicit or implicit through a reference is of fixed size

Notice that copies are more efficient if the compiler knows that it is copying from one generic to another as opposed to base to base

#include <iostream>
#include <sstream>
#include <vector>

class gen_base
{
public:
    virtual std::ostream & output(std::ostream& S) const = 0;
    virtual std::istream & input(std::istream& S) = 0;

    friend std::istream & operator >> (std::istream &S, gen_base &g) {
        return g.input(S);
    }

    friend std::ostream & operator << (std::ostream &S, const gen_base &g) {
        return g.output(S);
    }
};

template<typename T>
class GenericContainer : public gen_base
{
public:
    GenericContainer(T data) : _data(data) {}
    GenericContainer(const gen_base& other) {
// std::cout << "EXPENSIVE" << std::endl;
        std::stringstream cvt;
        other.output(cvt);
        input(cvt);
    }
    template <class U>
    GenericContainer(const GenericContainer<U>& other)
    {
// std::cout << "CHEAP" << std::endl;
        _data=other.getData();
    }
    virtual std::istream & input(std::istream &S) {
        return (S >> _data);
    }
    virtual std::ostream & output(std::ostream &S) const {
        return (S << _data);
    }
    T getData() const {
      return _data;
    }
private:
    T _data;
};

typedef GenericContainer<int> IntContainer;
typedef GenericContainer<bool> BoolContainer;
typedef GenericContainer<std::string> StringContainer;

int main(int argc, char const *argv[])
{
    IntContainer * intc = new IntContainer(42);
    std::cout << *intc << std::endl;

    gen_base * boolc = new BoolContainer(*intc);
    std::cout << *boolc << std::endl;

    IntContainer * intc2 = new IntContainer(*boolc);
    std::cout << *intc2 << std::endl;

    std::vector<gen_base *> v; // has to be pointer to base;
    v.push_back(intc);
    v.push_back(boolc);
    v.push_back(intc2);

    for (std::vector<gen_base *>::iterator it = v.begin() ; it != v.end(); ++it)
        std::cout << **it << std::endl;

    delete intc;
    delete boolc;

    return 0;
}

Upvotes: 1

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275700

This is a solution for any types of classes that can round-trip through a stringstream, and such conversion is the right way to convert between types. It isn't efficient at all:

struct BaseContainer {
protected:
  boost::any data;
  std::function< std::string( boost::any const& ) > toString;
  virtual void setDataAny( boost::any x, std::function< std::string( boost::any const& ) > convert ) {
    data = x;
    toString = convert;
  }
public:
  virtual boost::any getDataAny() const {
    return data;
  }
  template<typename T>
  void setData( T const& t ) {
    setDataAny( boost::any(t), []( boost::any const& a )->std::string {
      std::string retval;
      std::stringstream ss;
      try
      {
        ss << boost::any_cast< T >(a);
        ss >> retval;
        return retval;
      } catch(const boost::bad_any_cast &) {
        return retval;
      }
    });
  };
template<typename T>
struct TypedContainer:BaseContainer {
public:
  T getData() const {
    T retval;
    try {
      retval = boost::any_cast<T>(getDataAny());
      return retval;
    } catch(const boost::bad_any_cast &) {
      std::string str = toString( getDataAny() );
      std::stringstream ss;
      ss << str;
      ss >> retval;
      return retval;
    }
  }
};

with fewer types, you could do something similar, so long as you have conversion functions between them.

Alternatively, if you like exceptions, you could throw.

Alternatively, you could use boost::variants, which do no conversions, but work from a finite list of types (they are basically tagged unions that support more types than C++03 lets union do, and with some nice semantics on assign/copy/etc).

Upvotes: 1

stardust
stardust

Reputation: 5998

How about making base template too? Of course there is no way you can do something like

std::vector<Base> v;
v.push_back(intf);
v.push_back(boolf);

but the rest you can achieve with something simple as

template<typename T>
class Base
{
public:
    virtual void setData(T data) = 0;
    virtual T getData() = 0;
};

// Modify GenericContainer's definition like so
template<typename T>
class GenericContainer : Base<T> { 
    T d;
    public:
    virtual void setData(T data) {d = data;}
    virtual T getData() { return d; }
};

You can use it in any way as long as types match.

IntContainer intc = IntContainer();
intc.setData(42);
std::cout << intc.getData() << std::endl;

BoolContainer boolc = BoolContainer();
boolc.setData(true);
std::cout << boolc.getData() << std::endl;

std::vector<IntContainer> v;
v.push_back(intc);
// v.push_back(boolc); No can't do.

Upvotes: 3

syam
syam

Reputation: 15069

There is simply no way to do what you want.

The problem is, if this was allowed, the compiler would have to generate as many virtual methods in the base class as there are possible specializations of the template child class (ie. an infinity) which is not possible.

Upvotes: 5

Related Questions