Home > front end >  declare a template vector member in a class without template the class?
declare a template vector member in a class without template the class?

Time:05-13

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); }
};
  • Related