Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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 ?

like image 276
Guillaume Algis Avatar asked Apr 26 '13 16:04

Guillaume Algis


4 Answers

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.

like image 196
syam Avatar answered Oct 08 '22 20:10

syam


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.
like image 34
stardust Avatar answered Oct 08 '22 20:10

stardust


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

like image 1
Yakk - Adam Nevraumont Avatar answered Oct 08 '22 21:10

Yakk - Adam Nevraumont


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;
}
like image 1
Glenn Teitelbaum Avatar answered Oct 08 '22 19:10

Glenn Teitelbaum