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
{
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::variant
s, which do no conversions, but work from a finite list of types (they are basically tagged union
s that support more types than C++03 lets union
do, and with some nice semantics on assign/copy/etc).
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.
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.
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;
}