J-M. Gorius
J-M. Gorius

Reputation: 786

Member variable alias in class template specialization

Let's suppose I'm writing a Vector template class to represent points and vectors in an N-dimensional space. Something like the following:

template <typename T, int N>
struct Vector
{
    T data[N];
    // ...
};

Let's further assume that, for whatever reason, I want the user to be able to access data with meaningful names in the case of smaller vectors, e.g. by using v.xor v.y instead of v.data[0] and v.data[1].

I have two additional constraints.

  1. Access to the x or y component of a vector should not be written as a function call (e.g. it should be v.x, not v.x()).
  2. The following equality must hold sizeof(Vector<T, N>) == N * sizeof(T).

I looked at different possible approaches, including member variable references, tag dispatch and even CRTP but none of them satisfied all of my requirements.

Is it even possible to create such aliases? And if yes, how could one do it?

Upvotes: 4

Views: 973

Answers (3)

Amir Kirsh
Amir Kirsh

Reputation: 13810

If macros are allowed then it seems doable.

First attempt (nice but not perfect...)

int main() {
    Vector<int, 4> vec;
    vec[0] = 1; // same as: vec.t1 = 1;
    vec[1] = 2; // same as: vec.t2 = 2;
    vec[2] = 3; // same as: vec.t3 = 3;
    vec[3] = 4; // same as: vec.t4 = 4;
    std::cout << vec.t1 + vec.t2 + vec.t3 + vec.t4; // 10
}

To achieve the above:

#define VAR_NAME(num) t##num

#define DefineVector(num) \
    template<typename T> \
    struct Vector<T, num> : Vector<T, num-1> { \
        T VAR_NAME(num); \
        T& operator[](int index) { \
            if(index == num-1) return VAR_NAME(num); \
            return Vector<T, num-1>::operator[](index); \
        } \
    }

template<typename T, size_t N>
struct Vector;

template<typename T>
struct Vector<T, 1> {
    T t1;
    T& operator[](int index) {
        // in case index != 0 this is UB
        return t1;
    }
};

DefineVector(2);
DefineVector(3);
DefineVector(4);

// TODO:
// replace 3 declarations above with a single *DefineVectorsRecursively(4);*
// by using recursive macros
// see: https://stackoverflow.com/questions/12447557/can-we-have-recursive-macros
// leaving this as a further exercise...

http://coliru.stacked-crooked.com/a/42625e9c198e1e58

EDIT: Added operator[] to address the concern raised in comment.


Second attempt: with nicer field names

The OP requested the fields to have nicer names, like x, y, z.

This is a challenge. But macros come again to the rescue:

int main() {
    Vector<int, 3> vec;
    vec[0] = 1;
    vec[1] = 2;
    vec[2] = 3;
    std::cout << vec.x + vec.y + vec.z; // 6
}

With the following code:

#include <boost/preprocessor/variadic/size.hpp>

template<typename T, size_t DIMENSIONS>
struct Vector;

#define DefineVector(VAR, ...) \
    template<typename T> \
    struct Vector<T, BOOST_PP_VARIADIC_SIZE(__VA_ARGS__) + 1> \
      : Vector<T, BOOST_PP_VARIADIC_SIZE(__VA_ARGS__)> { \
        T VAR; \
        T& operator[](int index) { \
            if(index == BOOST_PP_VARIADIC_SIZE(__VA_ARGS__)) return VAR; \
            return Vector<T, BOOST_PP_VARIADIC_SIZE(__VA_ARGS__)>::operator[](index); \
        } \
    }

#define DefineVector1(VAR) \
    template<typename T> \
    struct Vector<T, 1> { \
        T VAR; \
        T& operator[](int index) { \
            /* in case index != 0 this is UB */ \
            return VAR; \
        } \
    }

DefineVector1(x);
DefineVector(y, x);
DefineVector(z, y, x);
// TODO: create recursive macro for DefineVector(z, y, x)
// that will create the two above recursively

Code: http://coliru.stacked-crooked.com/a/2550eede71dc9b5e


But wait, operator[] is not so efficient

I had a thought of having a more efficient operator[] in case T is a standard layout type, with the following implementation:

#define DefineVector(VAR, ...) \
    template<typename T> \
    struct Vector<T, BOOST_PP_VARIADIC_SIZE(__VA_ARGS__) + 1> \
      : Vector<T, BOOST_PP_VARIADIC_SIZE(__VA_ARGS__)> { \
        T VAR; \
    T& operator[](int index) { \
        if constexpr(std::is_standard_layout_v<T>) { \
            return *(&VAR - (BOOST_PP_VARIADIC_SIZE(__VA_ARGS__) - index)); \
        } else { \
            if(index == BOOST_PP_VARIADIC_SIZE(__VA_ARGS__)) return VAR; \
            return Vector<T, BOOST_PP_VARIADIC_SIZE(__VA_ARGS__)>::operator[](index); \
        } \
    } \
}

The (BAD) attempt: http://coliru.stacked-crooked.com/a/d367e770f107995f

Unfortunately - above optimization is illegal

For the presented implementation, any Vector with DIMENSIONS > 1 is not a standard layout class (even if T is) because it has members both in base class and in the derived class:

10.1 [class.prop]

[3] A class S is a standard-layout class if it: ...

[3.6] has all non-static data members and bit-fields in the class and its base classes first declared in the same class ...

So the optimization attempt above has undefined behavior - compiler is not obliged to keep the address of the members in the inheritance hierarchy in their order.

The initial solution is still valid.

Upvotes: 0

Georgi Gerganov
Georgi Gerganov

Reputation: 1066

Here is possible solution (although I think this is bad practice, and not really sure if portable):

template <typename T, int N>
union Vector
{
    struct { T x, y, z; };
    T data[N];
};

Here is example what happens:

int main() {
    Vector<int, 10> vec;
    vec.x = 100;
    vec.y = 200;
    vec.z = 300;
    vec.data[3] = vec.data[2] + 100;

    printf("%d %d %d %d\n", vec.data[0], vec.data[1], vec.data[2], vec.data[3]);
    printf("size = %d\n", (int) sizeof(vec));

    return 0;
}

Output:
    100 200 300 400
    size = 40

Update: and to make this well defined you can do:

template <typename T, int N> union Vector;

template <typename T> union Vector<T, 1> {
    struct { T x; };
    T data[1];
};

template <typename T> union Vector<T, 2> {
    struct { T x, y; };
    T data[2];
};

template <typename T> union Vector<T, 3> {
    struct { T x, y, z; };
    T data[3];
};

template <typename T> union Vector<T, 4> {
    struct { T x, y, z, w; };
    T data[4];
};

Just make sure the struct is standard-layout (i.e. this works for T = int, float, double, etc.).


Update 2: note that the above might still be UB, because T x, y, z and T data[3] seem to not actually be layout-compatible (see here). Still, this pattern seems to be used in various libraries, for implementing simple vector types - example1 (GLM), example2 video, example3

Upvotes: -1

Eljay
Eljay

Reputation: 5321

(This is not an answer, it is a comment with a code example, which doesn't fit as a comment, and doesn't format well if it could be stuffed into a comment.)

Can you go the other direction, and express the vector as a bunch of fields, then map an index getter/setter to each of those fields?

Taking out the N template parameter to simplify the problem:

#include <iostream>
#include <stdexcept>

template <typename T>
struct Vector3
{
    T x;
    T y;
    T z;
    T operator[](int i) const
    {
        switch(i)
        {
            case 0:
                return x;
            case 1:
                return y;
            case 2:
                return z;
            default:
                throw std::out_of_range("out of range");
        }
    }
    T& operator[](int i)
    {
        switch(i)
        {
            case 0:
                return x;
            case 1:
                return y;
            case 2:
                return z;
            default:
                throw std::out_of_range("out of range");
        }
    }
};

int main()
{
    Vector3<float> v;
    v.x = 1.0f;
    v[1] = 2.0f;
    v.z = 3.0f;
    std::cout << v[0] << " " << v.y << " " << v[2] << '\n';
}

Upvotes: 2

Related Questions