0

Probably not the wisest choice, but modelling a Vertex<T, N> class to abstract Vertex2<T>, Vertex3<T> and Vertex4<T> implementations, providing basic access. It's structured like this:

template<typename T, unsigned N>
class _Vertex {
    const unsigned _size = N;
    T _data[N];

public:
    inline _Vertex() : _data() {};

    inline T const& operator[](int pos) const { return _data[pos]; } 
    inline T      & operator[](int pos)       { return _data[pos]; }
};

Say I want to implement Vertex2<T> as a Vertex<T, 2>, and provide aliases like x and y. The most proper way would be adding functions like:

 inline T const& x() const { return (*this)[0]; }
 inline T      & x()       { return (*this)[0]; }

This would be repeated for every property, or alias I'd like to add. It's not a bad design, but usage proves tricky as, provided v is of type Vector2<float>, v.x() = 1.0f is not as friendly as v.x = 1.0f.

Is there any way to provide clearer, friendlier alias?

My main thoughts were "abusing" memory layout, providing access to _data[i] accordingly, but I have no idea where to start.

This question is based on my "re-imagination" of a vertex.h header file provided for an assigment, so this makes it related to homework, but I can assure you the end is not. It's just my curiosity holding me off doing my homework!

Alxe
  • 381
  • 3
  • 12
  • `_size` should be `static` to avoid issues with the assignment operator. You should probably also make it `constexpr`. – nwp Oct 05 '17 at 18:26
  • regardless of the question, you want to mark your `operator []` as `noexcept` as it never throws an exception – David Haim Oct 05 '17 at 18:26
  • A way to add data members is to make specializations for `_Vertex<1>` to `_Vertex<3>`. That would have the added benefit that `_Vertex<1>` doesn't have a member `y`. But it is probably not worth the code duplication required to make it work. – nwp Oct 05 '17 at 18:30
  • Unrelated: Careful with names like `_Vertex`. Underscore followed by Capital Letter is reserved for use by the library. More here: [What are the rules about using an underscore in a C++ identifier?](https://stackoverflow.com/questions/228783/what-are-the-rules-about-using-an-underscore-in-a-c-identifier) – user4581301 Oct 05 '17 at 18:34
  • @nwp I guessed, and actually implemented it at first. It makes more sense, too, as every vector of the class would have the same length/size – Alxe Oct 05 '17 at 18:41
  • @David Haim why that? Can you elaborate, maybe with a link? – Alxe Oct 05 '17 at 18:42
  • @user4581301 so, should I change it to lowercase? I was thinking of underscoring the "private" ones and not underscoring the "public" ones (shown with using statements) – Alxe Oct 05 '17 at 18:45
  • Use something other than an underscore or stick the underscore at the end. Read the link and you'll see there are a whole bunch of rules. At global scope `_vertex` is reserved, but if you pop it in a namespace you're good. Underscore+Capital can get you at any scope as can two underscores in a row anywhere in an identifier. Personally, I just never use a preceding underscore rather than try to remember exactly when I can and cannot use one. – user4581301 Oct 05 '17 at 19:21
  • @user4581301 I'd use namespaces, so I guess I'd be good. I don't quite like "tail" underscores, as an IDE can pop them up, while a "head" underscore would not. Thanks for the info – Alxe Oct 05 '17 at 20:21

2 Answers2

1

I put on my robe and Yakk hat.

operator->* is clearly underutilized in C++. We can overload to take a tag type of our choosing in a way that kind of looks like member access. Here's a simplified example:

template <size_t I> struct index_t { };
constexpr index_t<0> x{};
constexpr index_t<1> y{};

struct Vector {
    int data[2];

    template <size_t I>        
    int& operator->*(index_t<I> ) { return data[I]; }

    int& operator[](size_t idx) { return data[idx]; }
};

With that, we can do:

int main()
{
    Vector v{1, 2};
    std::cout << v.data[0] << ' ' << v.data[1] << '\n'; // 1 2
    v->*x = 42;
    v->*y = 17;
    std::cout << v.data[0] << ' ' << v.data[1] << '\n'; // 42 17
}

That said, don't do this. C++ doesn't have properties. The right way to do this would just be named functions that return lvalue references:

int& x();
int& y();

It may not be as "nice" as v.x but it gets the job done without having to add extra members to your class.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • Wow, that's kinda funky. I really like how you could do a bunch of aliases for the operator to take. But it is rough as you couldn't use x or y again unless you dealt with hierarchy black magic. And a strange operator, as `v` is not a pointer. Summarizing, black evil voodoo! Quite curious. – Alxe Oct 05 '17 at 20:40
  • As noone has given me a proper solution (if there's any) to my question, I'll be aproving your answer as, even if it's the same I said in the question, you gave me some thought-food. – Alxe Oct 06 '17 at 16:05
0

v.x = 2.f is not the OOP/C++ way of member access. You are best off using get/set function like t = v.x() and v.x( t ).

It is possible to defeat the data encapsulation using the following:

template< class T >
class Vertex2 : private Vertex< T, 2 >
{
public:
   T & x;
   T & y;
   Vertex2(): Vertex< T, 2 >(), x((*this)[0]), y((*this)[1]) {}
};

This doesn't provide a const access but if you want to do v.x = you probably don't care too much about such things. Unfortunately, you are probably stuck with the extra memory of the two references.

Justin Finnerty
  • 329
  • 1
  • 7
  • Two references could add up a bunch of memory in a, say, 10e3 array of vertices. Also, what would be better, `v.x(value)` or `v.x() = value` for a simple Data Object like a vertex, which behaves kinda like a struct (public members)? – Alxe Oct 05 '17 at 20:18
  • I always use `v.x(value)`. This allows you to later do checks on the assigned value or more easily change the underlying implementation. For example if you are thinking of storing the Vertex objects in a container and doing sequential operations then using a VertexContainer with arrays for the x, y etc values will be substantially faster (2-3x). In that case Vertex might just be a glorified iterator into the VertexContainer. – Justin Finnerty Oct 05 '17 at 21:30