3

This is a simple question, and I am sure that it has been answered before but I cannot seem to find a good answer.

I have a class, Point:

template<class T>
Point{
\\code
}

...and now I want a vector of Points, some of which have T as an integer which have T as a double. I want to write something like

template<class T>
std::vector<Point<T> > points;

But, alas, this doesn't compile with the error "expected primary-expression before 'template'". I haven't been able to fidget with this code to make it work. Also relevant is that points is in the main class, so I can't stick the template declaration outside the function.

If someone could direct me to a solution, I would be much obliged.

Thanks.

alexvas
  • 695
  • 2
  • 8
  • 23
  • 2
    A vector can only hold elements of one type. So you can choose Point or Point but you can not mix them. – Martin York Jun 12 '12 at 07:12
  • Shouldn't that just be `std::vector > points`? – knittl Jun 12 '12 at 07:12
  • Do you want a templated `typedef` ? – ereOn Jun 12 '12 at 07:14
  • 1
    Different instantiations of a class template are completely unrelated types: `Point` and `Point`, from the compiler point of view, are two different types with nothing in common. If you want to manipulate identically various types of points, you'll need to use a common base class. – Luc Touraille Jun 12 '12 at 07:25
  • 1
    @LucTouraille: Using inheritance would force the OP to use reference or pointers and it might not be a good choice for a "value-type" class (which I assume `Point` is). Perhaps the variant approach might be better in this case. – ereOn Jun 12 '12 at 07:27
  • @ereOn: Sure, a common base class would probably make no sense here (I don't see what kind of interface it could expose), but the OP seemed to think that somehow `Point` and `Point` were related, so I just wanted to clarify that they are not. – Luc Touraille Jun 12 '12 at 07:33

2 Answers2

5

If your goal is to have a vector that holds both Point<int> and Point<double> you can use Boost Variant.

typedef boost::variant<Point<int>, Point<double> > VariantPoint;

Then:

std::vector<VariantPoint> my_vector;

my_vector.push_back(Point<int>(1, 0));
my_vector.push_back(Point<double>(1.5f, 2.0f));

Will work. Note that to inspect the elements afterwards, you probably will have to use the visitor pattern as documented here.

If your goal is to have distinct vector types that can only hold one type of Point, then you may use:

template<typename T> using PointVector = std::vector<Point<T>>; // C++11

// Now you can write:
PointVector<int> my_vector;

// Which is equivalent to:
std::vector<Point<int>> my_vector;

Or, if C++11 is not an option:

template<typename T> struct PointVector
{
  typedef std::vector<Point<T> > Type;
}

Then:

PointVector<int>::Type my_vector;
ereOn
  • 53,676
  • 39
  • 161
  • 238
  • Thank you for the complete answer. From my understanding Boost and C++11 is not in the standard C++ libraries, and I am writing code that will be run on computers that don't necessarily have these packages. However, I am looking exactly for your first solution involving variants. Is there a way to do that without Boost? (Say, as the other answer suggested.) – alexvas Jun 12 '12 at 19:41
  • @alexvas: If you are comfortable with digging into the Boost Variant code, you can probably reproduce it easily. Or you can perhaps extract the files from Boost (Boost has a tool to extract cleanly subsets of it). Boost can run on many platforms, perhaps you can ship it with your own code (or convince those who decide to do so ?) – ereOn Jun 13 '12 at 06:57
2

To get a single kind of vector, I would use inheritance:

template <typename T>
struct PointVector : public std::vector< Point<T> >
{
};

Note, the inheritance is just a mechanism to achieve the equivalent of a template typedef. This means, PointVector should not contain any data members or virtual functions. However, @ereOn's suggestion is preferred, and is discussed in the answer to this question.

The old fashioned way to achieve a variant would be to use a union.

class IntOrDouble {
    union {
        int i;
        double d;
    };
    bool is_int;
    bool is_double;
public:
    IntOrDouble () : is_int(false), is_double(false) {}
    IntOrDouble (int x) : is_int(true), is_double(false) { i = x; }
    IntOrDouble (double x) : is_int(false), is_double(true) { d = x; }
    int operator = (int x) {
        is_int = true;
        is_double = false;
        return i = x;
    };
    double operator = (double x) {
        is_int = false;
        is_double = true;
        return d = x;
    };
    operator int () const {
        if (is_int) return i;
        if (is_double) return d;
        return 0;
    }
    operator double () const {
        if (is_double) return d;
        if (is_int) return i;
        return 0;
    }
};
typedef std::vector< Point<IntOrDouble> > PointVector;

But it all seems a little over the top for this use case. I'd just use vectors of double all around, unless memory was really tight.

Community
  • 1
  • 1
jxh
  • 69,070
  • 8
  • 110
  • 193
  • I won't downvote, but inheriting in this case is wrong for several reasons. The most important reason is that `std::vector` wasn't designed to be publicly inherited from. I'm not sure what the standard says, but I highly doubt it has a virtual destructor for instance, and that might lead to subtle problems. If anything, it should be private inheritance and still, I wouldn't recommend it. – ereOn Jun 12 '12 at 09:17
  • @ereOn: Thanks for the input. I have used inheritance of STL containers for a long time now, so I find it surprising to learn it could be unsafe. I'd be interested in any documentation you find that supports that. Regards. – jxh Jun 12 '12 at 09:26
  • @ereOn: I clarified my answer to indicate the inheritance is only used as a means to perform a template typedef. – jxh Jun 12 '12 at 09:35
  • Inheriting from `std::vector` is not forbidden by the standard (obviously), but it does not have a virtual destructor. That is, if one were to `delete` a `PointVector` trough a pointer to the base class, it might cause memory leaks. Moreover, public inheritance in this case also allow to the OP to write `std::vector > vec = PointVector();` which causes [**slicing**](http://stackoverflow.com/questions/274626/what-is-the-slicing-problem-in-c), and you probably don't want that. The *union* solution is far more correct but if `boost::variant` is an option, its even better. – ereOn Jun 12 '12 at 10:07
  • @ereOn: Thanks, I got your concern about virtual destructor. Since the inheritance is meant to achieve the equivalent of a template typedef, there won't be any data members in the subclass, and I don't mind slicing, either, since a typedef is supposed to behave with equivalence. Regards. – jxh Jun 12 '12 at 10:11
  • @ereOn: found a link to another discussion, and I mentioned your method in my post as preferred. Your post gets +1 from me. Regards. – jxh Jun 12 '12 at 19:30