views:

702

answers:

3

Forgive me, for I am fairly new to C++, but I am having some trouble regarding operator ambiguity. I think it is compiler-specific, for the code compiled on my desktop. However, it fails to compile on my laptop. I think I know what's going wrong, but I don't see an elegant way around it. Please let me know if I am making an obvious mistake. Anyhow, here's what I'm trying to do:

I have made my own vector class called Vector4 which looks something like this:

class Vector4
{
 private:
   GLfloat vector[4];
 ...
}

Then I have these operators, which are causing the problem:

operator GLfloat* () { return vector; }

operator const GLfloat* () const { return vector; }

GLfloat& operator [] (const size_t i) { return vector[i]; }

const GLfloat& operator [] (const size_t i) const { return vector[i]; }

I have the conversion operator so that I can pass an instance of my Vector4 class to glVertex3fv, and I have subscripting for obvious reasons. However, calls that involve subscripting the Vector4 become ambiguous to the compiler:

enum {x, y, z, w}
Vector4 v(1.0, 2.0, 3.0, 4.0);

glTranslatef(v[x], v[y], v[z]);

Here are the candidates:

candidate 1: const GLfloat& Vector4:: operator[](size_t) const
candidate 2: operator[](const GLfloat*, int) <built-in>

Why would it try to convert my Vector4 to a GLfloat* first when the subscript operator is already defined on Vector4? Is there a simple way around this that doesn't involve typecasting? Am I just making a silly mistake? Thanks for any help in advance.

+1  A: 

It's too hard to get rid of the ambiguity. It could easily interpret it as the direct [] access, or cast-to-float* followed by array indexing.

My advice is to drop the operator GLfloat*. It's just asking for trouble to have implicit casts to float this way. If you must access the floats directly, make a get() (or some other name of your choice) method to Vector4 that returns a pointer to the raw floats underneath.

Other random advice: rather than reinvent your own vector classes, you should use the excellent ones in the "IlmBase" package that is part of OpenEXR

Larry Gritz
Thanks! I had no idea that conversion operators were such a bad idea. I guess since I'm pretty new to C++, I haven't gotten burned with them yet. I realized there must be tons of great vector and matrix classes out there, but I really decided to make my own as more of an exercise.
Scott
+6  A: 

This is explained in the book "C++ Templates - The Complete Guide". It's because your operator[] takes size_t, but you pass a different type which first has to undergo an implicit conversion to size_t. On the other side, the conversion operator can be chosen too, and then the returned pointer can be subscript. So there is the ambiguity. Solution is to drop the conversion operator. They should generally be avoided as they just introduce problems, as you see.

Provide a begin and end member function that returns vector and vector + 4 respectively. Then you can use v.begin() if you want to pass to native openGL functions.

There is a bit confusion in the comments. I think i will update this answer now to reflect the most recent concept of this.

struct Vector4 {
    // some of container requirements
    typedef GLfloat value_type;
    typedef GLfloat& reference;
    typedef GLfloat const& const_reference;

    typedef GLfloat * iterator;
    typedef GLfloat const * const_iterator;

    typedef std::ptrdiff_t difference_type;
    typedef std::size_t size_type;

    static const size_type static_size = 4;

    // returns iterators to the begin and end
    iterator begin() { return vector; }
    iterator end() { return vector + size(); }

    const_iterator begin() const { return vector; }
    const_iterator end() const { return vector + size(); }

    size_type size() const { return static_size; }
    size_type max_size() const { return static_size; }

    void swap(Vector4 & that) {
        std::swap(*this, that);
    }

    // some of sequences
    reference operator[](size_type t) { return vector[t]; }
    const_reference operator[](size_type t) const { return vector[t]; }

    // specific for us. returns a pointer to the begin of our buffer.
    // compatible with std::vector, std::array and std::string of c++1x
    value_type * data() { return vector; }
    value_type const* data() const { return vector; }

    // comparison stuff for containers
    friend bool operator==(Vector4 const&a, Vector4 const&b) {
        return std::equal(a.begin(), a.end(), b.begin());
    }
    friend bool operator!=(Vector4 const&a, Vector4 const&b) { return !(a == b); }
    friend bool operator<(Vector4 const&a, Vector4 const&b) {
        return std::lexicographical_compare(a.begin(), a.end(), b.begin(), b.end());
    }
    friend bool operator> (Vector4 const&a, Vector4 const&b) { return b < a;    }
    friend bool operator<=(Vector4 const&a, Vector4 const&b) { return !(b < a); }
    friend bool operator>=(Vector4 const&a, Vector4 const&b) { return !(a < b); }

private:
    GLfloat vector[4];
}
Johannes Schaub - litb
Thank you very much! I didn't even consider the size_t conversion! I really like your solution. It fits well with the stl conventions I have seen so far.
Scott
better you define a data() function that just returns vector too. and then you pass with data(). the reason is begin() returns an iterator, that is seen as a black box that just provides the facility to iterate, while data() returns a pointer to the actual data. boost::array follows the same design
Johannes Schaub - litb
Doh! Yeah, I agree. begin() and end() should return an iterator, like in the stl. I haven't come across data() yet, and only know a little about boost's conventions. Thanks for the tip! I think I need to familiarize myself with more of these conventions, and read the arguments for them.
Scott
A pointer fits all requirement for an iterator, and it is at the same time a pointer. I don't see any problem with using begin() to return a pointer. What would be the problems of the initial approach?
David Rodríguez - dribeas
dribeas, of course a pointer is an iterator. i didn't say otherwise. after all that is the reason you can give .begin() to STL algorithms. but the point is, an iterator is not always a pointer.
Johannes Schaub - litb
Johannes Schaub - litb
A: 

Why are you passing a "const size_t" to operator[] ?

BubbaT
There seems to be a concern with my use of const before the by-value size_t parameter. I know it is not necessary, but I think it makes it explicitly clear that the value should not change, as we are using it as an index. Can someone explain why this is so undesirable?
Scott
@Scott Is not undesirable but just unnecessary in many cases as copying of intrinsic types like size_t or int is no-cost operation and usually they are passed by value anyway, so no side-effects left outside function - such function argument is local to function. The const size_t is helpful to keep function self-describing and indicate variable never changes its value.
mloskot