I have some class that should be populated with values, but I don't know the type of values.
To clarify, each vector in a class instance is populated with the same value types,
But one instance of SomeClass can have vector<int>
and another vector<string>
and so on.
How can I declare the vector as template, but not template the class itself?
template<typename T>
struct tvector {
typedef std::vector< std::vector<T> > type;
};
class SomeClass {
public:
int _someField;
tvector<T> _fieldValues; // this line fails to compile
};
Thanks in advance
CodePudding user response:
There are a few different ways to shear this beast. It mostly depends on how you want to access this vector and how you determine its exact type at runtime.
std::variant
A variant can store a set of predetermined types. It's effective but also cumbersome if you have many different types because you have to funnel every access through some type checking.
class SomeClass {
public:
using variant_type = std::variant<
std::vector<int>, std::vector<double> >;
int _someField;
variant_type _fieldValues;
void print(std::ostream& stream) const
{
switch(_fieldValues.index()) {
case 0:
for(int i: std::get<0>(_fieldValues))
stream << i << ' ';
break;
case 1:
for(double i: std::get<1>(_fieldValues))
stream << i << ' ';
break;
default: break;
}
}
};
std::any
Any can hold literally any type. Improves extendability but makes working with the values hard.
class SomeClass {
public:
int _someField;
std::any _fieldValues;
void print(std::ostream& stream) const
{
if(_fieldValues.type() == typeid(std::vector<int>))
for(int i: std::any_cast<std::vector<int>>(_fieldValues))
stream << i << ' ';
else if(_fieldValues.type() == typeid(std::vector<double>))
for(double i: std::any_cast<std::vector<double>>(_fieldValues))
stream << i << ' ';
else
throw std::runtime_error("Not implemented");
}
};
Subclassing
The most elegant way (IMHO) is to use a templated subclass. Something like this:
class SomeClass {
public:
int _someField;
virtual ~SomeClass() = default;
virtual void print(std::ostream& stream) const = 0;
};
template<class T>
SomeClassT: public SomeClass
{
std::vector<T> _fieldValues;
public:
virtual void print(std::ostream& stream) const
{
for(const T& i: _fieldValues)
stream << i << ' ';
}
};
Or if you don't want to expose that part, make it a private member.
class SomeClassHelper {
public:
virtual ~SomeClassHelper() = default;
virtual void print(std::ostream& stream) const = 0;
};
template<class T>
SomeClassHelperT: public SomeClassHelper
{
std::vector<T> _fieldValues;
public:
virtual void print(std::ostream& stream) const
{
for(const T& i: _fieldValues)
stream << i << ' ';
}
};
class SomeClass {
public:
int _someField;
private:
std::unique_ptr<SomeClassHelper> helper;
public:
void print(std::ostream& stream) const
{ return helper->print(stream); }
};