Related
Sometimes there is a need to have a Vector3f class, which has x, y and z members, and can be indexed as a float[3] array at the same time (there are several questions here at SO already about this).
Something like:
struct Vector3f {
float data[3];
float &x = data[0];
float &y = data[1];
float &z = data[2];
};
With this, we can write this:
Vector3f v;
v.x = 2.0f;
v.y = 3.0f;
v.z = 4.0f;
glVertex3fv(v.data);
But this implementation is bad, because references take space in the struct (which is quite unfortunate. I don't see any reason why references cannot be removed in this particular case, maybe it is missed optimization from the compiler's part).
But, with [[no_unique_address]] I had this idea:
#include <new>
template <int INDEX>
class Vector3fProperty {
public:
operator float() const {
return propertyValue();
}
float &operator=(float value) {
float &v = propertyValue();
v = value;
return v;
}
private:
float &propertyValue() {
return std::launder(reinterpret_cast<float*>(this))[INDEX];
}
float propertyValue() const {
return std::launder(reinterpret_cast<const float*>(this))[INDEX];
}
};
struct Vector3f {
[[no_unique_address]]
Vector3fProperty<0> x;
[[no_unique_address]]
Vector3fProperty<1> y;
[[no_unique_address]]
Vector3fProperty<2> z;
float data[3];
};
static_assert(sizeof(Vector3f)==12);
So, basically, I have properties in the struct, which handles the access to x, y and z. These properties should not take space, as they are empty, and have the attribute of [[no_unique_address]]
What do you think about this approach? Does it have UB?
Note, this question is about a class, for which all these are possible:
Vector3f v;
v.x = 1;
float tmp = v.x;
float *c = v.<something>; // there, c points to a float[3] array
If this is going to live in a header, and you have some confidence in your compiler's optimizing capabilities, you can probably stick to a plain-old operator[]() overload and expect the compiler to be smart enough to elide the call and return the element that you want. E.g.:
class Vec3f {
public:
float x;
float y;
float z;
float &operator[](int i) {
if(i == 0) {
return x;
}
if(i == 1) {
return y;
}
if(i == 2) {
return z;
}
}
};
I tossed this into Compiler Explorer (https://godbolt.org/z/0X4FPL), which showed clang optimizing the operator[] call away at -O2, and GCC at -O3. Less exciting than your approach, but simple and should work under most circumstances.
But this implementation is bad, because references take space in the struct (which is quite unfortunate. I don't see any reason why references cannot be removed in this particular case, maybe it is missed optimization from the compiler's part).
This looks like a complicated issue. Standard-layout classes have to be compatible between each other. And so compilers are not allowed to eliminate any member, regardless of how they are defined. For non standard-layout? Who knows. For more info read this: Do the C++ standards guarantee that unused private fields will influence sizeof?
From my experience compilers never remove class members, even if they are "unused" (e.g. formally sizeof does use them).
Does it have UB?
I think this is UB. First of all [[no_unique_address]] only means that the member need not have a unique address, not that it must not have a unique address. Secondly it is not clear where your data member starts. Again, compilers are free to use or not paddings of previous [[no_unique_address]] class members. Meaning your accessors may access incorrect piece of memory.
Another problem is that you want to access "outer" memory from the "inner" class. AFAIK such thing is also UB in C++.
What do you think about this approach?
Assuming it is correct (which is not) I still don't like it. You want getters/setters but C++ does not support this feature. So instead of doing those weird, complicated constructs (imagine other people maintaining this code) how about simply do
struct Vector3f {
float data[3];
float x() {
return data[0];
}
void x(float value) {
data[0] = value;
}
...
};
You say this code is ugly. Maybe it is. But it is simple, easy to read and maintain. There's no UB, it does not depend on potential hacks with unions, and does exactly what you want, except for beauty requirement. :)
GLM implements this kind of functionality using anonymous structs inside an anonymous union
I can't personally guarantee that this is standard-compliant, but most major compilers (MSVC, GCC, Clang) will support this idiom:
struct Vector3f {
union {
struct {
float x, y, z;
};
struct {
float data[3];
};
};
Vector3f() : Vector3f(0,0,0) {}
Vector3f(float x, float y, float z) : x(x), y(y), z(z) {}
};
int main() {
Vector3f vec;
vec.x = 14.5;
std::cout << vec.data[0] << std::endl; //Should print 14.5
vec.y = -22.345;
std::cout << vec.data[1] << std::endl; //Should print -22.345
std::cout << sizeof(vec) << std::endl; //On most platforms will print 12
}
The non-standard behavior is in the anonymous struct used to group the letters together, which GCC will issue a warning about. As far as I know, the union itself should be valid, because the datatypes are all identical, but you should still check with your compiler documentation if you're unsure whether this is valid or not.
As an added convenience, we can also overload the brackets operator to shorten our syntax a little:
struct Vector3f {
/*...*/
float& operator[](size_t index) {return data[index];}
float operator[](size_t index) const {return data[index];}
};
int main() {
Vector3f vec;
vec.x = 14.5;
std::cout << vec[0] << std::endl; //Should print 14.5
vec.y = -22.345;
std::cout << vec[1] << std::endl; //Should print -22.345
std::cout << sizeof(vec) << std::endl; //On most platforms will print 12
}
Just for clarity, accessing inactive members in the way I am is valid according to the C++ standard, because those members share a "common subsequence":
If two union members are standard-layout types, it's well-defined to examine their common subsequence on any compiler.
CPP Reference: Union Declaration
Because x and data[0] are
Both floats,
Both occupy the same memory,
Are both standard Layout types as the standard defines them,
It's perfectly valid to access one or the other regardless of which is currently active.
As stated, this is impossible: pointer arithmetic is defined only within an array, and there’s no way (without putting a reference in the class, which takes up space in current implementations) to have v.x refer to an array element.
For the example below, what may cause undefined behavior? and why?
#include <cstddef>
#include <iostream>
template <typename Ty>
bool in_range(const Ty *test, const Ty *r, size_t n)
{
return 0 < (test - r) && (test - r) < (std::ptrdiff_t)n;
}
void f() {
double foo[10];
double *x = &foo[0];
double bar;
std::cout << std::boolalpha << in_range(&bar, x, 10);
}
I have not found the answer in When is pointer subtraction undefined in C?
Pointer arithmetic, including the subtraction of two pointers, is only defined if the pointers point to elements within the same array, or one past the end of that array. In this context, a scalar counts as an array of size 1.
It's pretty pointless to allow pointer arithmetic in any other instance. To do that would unnecessarily constrain the C's memory model and could curtail its flexibility and ability to port to exotic architectures.
For the code, as you've written it, the answer is basically the same for C++ as it is for C: you get defined behavior if and only if the two pointers involved refer to parts of the same array, or one past its end (where, as #bathsheba already noted, a non-array object is treated as being the same as an array of one item).
C++ does, however, add one wrinkle that might be useful to know about here: even though neither subtraction nor ordered comparisons (e.g., <) is required to produce meaningful results when applied to pointers to separate objects, std::less<T> and friends, from <functional> are required to do so. So, given two separate objects like this:
Object a;
Object b;
...comparing the addresses of the two objects with the comparison objects must "yield a strict total order that is consistent among those specializations and is also consistent with the partial order imposed by the built-in operators <, >, <=, >=." (N4659, [comparisons]/2).
As such, you could write your function something like this:
template <typename Ty>
bool in_range(const Ty *test, const Ty *begin, const Ty *end)
{
return std::less_equal<Ty *>()(begin, test) && std::less<Ty *>()(test, end);
}
If you really want to maintain the original function signature, you could do that as well:
template <typename Ty>
bool in_range(const Ty *test, const Ty *r, size_t n)
{
auto end = r + n;
return std::less_equal<Ty *>()(r, test) && std::less<Ty *>()(test, end);
}
[Note that I've written it using std::less_equal for the first comparison, and std:less for the second to match the typically expected semantics of C++, where the range is defined as [begin, end). ]
This does carry one proviso though: you need to ensure that r points to to the beginning of an array of at least n items1, or else the auto end = r + n; will produce undefined behavior.
At least for what I'd expect as the typical use-case for such a function, you can probably simplify usage a little but by passing the array itself, rather than a pointer and explicit length:
template <class Ty, size_t N>
bool in_range(Ty (&array)[N], Ty *test) {
return std::less_equal<Ty *>()(&array[0], test) &&
std::less<Ty *>()(test, &array[0] + N);
}
In this case, you'd just pass the name of the array, and the pointer you want to test:
int foo[10];
int *bar = &foo[4];
std::cout << std::boolalpha << in_range(foo, bar) << "\n"; // returns true
This only supports testing against actual arrays though. If you attempt to pass a non-array item as the first parameter it simply won't compile:
int foo[10];
int bar;
int *baz = &foo[0];
int *ptr = new int[20];
std::cout << std::boolalpha << in_range(bar, baz) << "\n"; // won't compile
std::cout << std::boolalpha << in_range(ptr, baz) << "\n"; // won't compile either
The former probably prevents some accidents. The latter might not be quite as desirable. If we want to support both, we can do so via overloading (for all three situations, if we choose to):
template <class Ty, size_t N>
bool in_range(Ty (&array)[N], Ty *test) {
return std::less_equal<Ty *>()(&array[0], test) &&
std::less<Ty *>()(test, &array[0]+ N);
}
template <class Ty>
bool in_range(Ty &a, Ty *b) { return &a == b; }
template <class Ty>
bool in_range(Ty a, Ty b, size_t N) {
return std::less_equal<Ty>()(a, b) &&
std::less<Ty>()(b, a + N);
}
void f() {
double foo[10];
double *x = &foo[0];
double bar;
double *baz = new double[20];
std::cout << std::boolalpha << in_range(foo, x) << "\n";
std::cout << std::boolalpha << in_range(bar, x) << "\n";
std::cout << std::boolalpha << in_range(baz, x, 20) << "\n";
}
1. If you want to get really technical, it doesn't have to point to the beginning of the array--it just has to point to part of an array that's followed by at least n items in the array.
Undefined behavior in this case usually does not cause a crash, but a meaningless or inconsistent result.
In most modern architectures, subtracting 2 unrelated pointers just computes the difference of addresses divided by the size of the pointed type, approximately this:
A *p1, *p2;
...
ptrdiff_t diff = ((intptr_t)p2 - (intptr_t)p1) / (intptr_t)sizeof(*p1);
Examples of architectures where the behavior would be unexpected are Intel's 16 bit segmented medium and large models:
These models were once prevalent on PCs, before the 386 came along and its 32-bit model.
far pointers were stored in 2 parts: a 16-bit segment (or selector in protected mode) and a 16-bit offset.
comparing 2 pointers for equality required 2 separate compare instructions and conditional jumps for the segment and the offset.
comparing a pointer to NULL was usually optimized as a single comparison of the segment part with 0
subtracting 2 pointers and comparing for relative position was performed on the offset part only, making the silent assumption that both pointers pointed to the same array, hence had the same segment.
in your example, both objects have automatic storage, so they are both in the same segment, pointed to as SS, but for 2 objects allocated from the heap you could have p <= q && q <= p and p != q at the same time, or p - q == 0 with p != q which is covered by undefined behavior.
In profiling my program I realized that 10% of the code is spent in a stupid std::complex<double>() constructor, using new std::complex<double>[size_of_array].
I have searched through the web and the default constructor for std::complex seems to take as real and imaginary parts the values double(). Since C++ does not initialize double numbers, I wonder why g++ bothers to initialize std::complex with zeros, and whether I could work around this through the whole program in some way (*)
(*) right now I have to special case the functions that create arrays of complex numbers to allocate uninitialized arrays of doubles and recast them as complex.
Edit: as pointed below, it was an oversight on my side. The default constructor has empty constructors for the real and imaginary part (http://en.cppreference.com/w/cpp/numeric/complex/complex)
complex( const T& re = T(), const T& im = T() );
but the specification then introduces special cases for double
complex(double re = 0.0, double im = 0.0);
It is this special case that introduces all the overhead, as it bypasses the actual default constructor of 'double' which does nothing (same as for int, long, float, etc).
I wonder why g++ bothers to initialize std::complex with zeros
Because the standard says it must do so, the default constructor is declared as:
constexpr complex(double re = 0.0, double im = 0.0);
so it sets both the members to zero.
It is normal for the standard library to safely initialize types, rather than leaving them uninitialized as you get with built-in types such as double and int*, for instance std::vector<double> zero-initializes its elements too if you resize it so that new elements get added. You can control this for vector by not adding elements to the vector until you know what values you want them to have.
One possible workaround for complex is to use a type that doesn't do the initialization:
struct D
{
D() noexcept { }; // does not initialize val!
D(double d) noexcept : val(d) { }
operator double() const noexcept { return val; }
D& operator=(double d) noexcept { val = d; return *this; }
double val;
};
Now if you use std::complex<D> the default constructor does nothing. Add explicit to the converting constructor and/or the conversion operator to suit your taste.
There is an easy way of doing it. If you "reserve" the memory with a std::vector its much faster because it doesn't call the constructor on each element.
ie this:
std::vector< std::complex< double > > vec;
vec.reserve( 256 );
for( int i = 0; i < 256; i++ )
{
vec.push_back( std::complex< double >( 1, 1 ) );
}
will be significantly faster than this:
std::complex< double >* arr = new std::complex< double >[256];
for( int i = 0; i < 256; i++ )
{
arr[i]( std::complex< double >( 1, 1 ) );
}
delete[] arr;
because the constructor is only called once in the first example.
It has the added advantage that you have RAII on your side and 'vec' will automatically be released when its out of scope.
The following is from code I am developing.
Added later: As M.M. noted in the comments, the behavior is technically undefined. Now I see that if some sleazy weasel were to change the implementation of std::complex so that it could not be trivially constructed / destructed, this would come a cropper. See also the example at http://en.cppreference.com/w/cpp/types/aligned_storage.
#include <complex>
#include <type_traits>
typedef std::complex<double> complex;
// Static array of complex that does not initialize
// with zeros (or anything).
template<unsigned N>
struct carray {
typedef
std::aligned_storage<sizeof(complex), alignof(complex)>::type raw;
raw val[N];
complex& operator[] (unsigned idx) {
return reinterpret_cast<complex&> (val[idx]);
}
complex* begin() { return &operator[](0); }
complex* end() { return &operator[](N); }
};
const int ADJ_MATRIX[VERTEX_NUM][VERTEX_NUM]={
{0,1,1,0,0,0,0,0},
{1,0,0,1,1,0,0,0},
{1,0,0,0,0,1,1,0},
{0,1,0,0,0,0,0,1},
{0,1,0,0,0,0,0,1},
{0,0,1,0,0,0,1,0},
{0,0,1,0,0,1,0,0},
{0,0,0,1,1,0,0,0}
};
typedef struct {
int vertex;
int matrix[VERTEX_NUM][VERTEX_NUM];
int vNum;
int eNum;
}Graph;
void buildGraph(Graph *graph){
graph->vNum = VERTEX_NUM;
graph->eNum = EDGE_NUM;
graph->matrix = ADJ_MATRIX;
}
The error occurs in this sentence:
graph->matrix = ADJ_MATRIX;
I am new to c++. please tell me why this problem occur and how to solve it?
I want to assign ADJ_MATRIX to the matrix in struct.
As was said, you can't assign arrays in C++. This is due to the compiler being a meanie, because the compiler can. It just won't let you do it...
... unless you trick it ;)
template <typename T, int N>
struct square_matrix {
T data[N][N];
};
square_matrix<int, 10> a;
square_matrix<int, 10> b;
a = b; // fine, and actually assigns the .data arrays
a.data = b.data; // not allowed, compiler won't let you assign arrays
The catch? Now the code needs some little things:
const square_matrix<int, VERTEX_NUM> ADJ_MATRIX={{
// blah blah
}}; // extra set of braces
typedef struct {
int vertex;
square_matrix<int, VERTEX_NUM> matrix;
int vNum;
int eNum;
}Graph;
void buildGraph(Graph *graph){
graph->vNum = VERTEX_NUM;
graph->eNum = EDGE_NUM;
graph->matrix = ADJ_MATRIX; // no change
}
And to access the cells, now we need to use graph->matrix.data[1][2]. This can be mitigated by overloading operator[] or operator() for square_matrix. However, this is now getting terribly close to the new std::array class, or the Boost equivalent boost::array, so it might be wise to consider those instead.
Unfortunately (or maybe fortunately, who knows...) you can't just assign one array to another in C++.
If you want to copy an array, you will need to either copy each of it's elements into a new array one by one, or use the memcpy() function:
for( int i = 0; i < VERTEX_NUM; i++ )
for( int j = 0; j < VERTEX_NUM; j++ )
graph->matrix[i][j] = ADJ_MATRIX[i][j];
or
memcpy( graph->matrix, ADJ_MATRIX, VERTEX_NUM * VERTEX_NUM * sizeof(int) );
Arrays are not assignable. You can use memcpy:
memcpy(graph->matrix, ADJ_MATRIX, sizeof(graph->matrix));
You cannot assign an array to another array. You will need to copy the elements from the source to the destination index by index, or use memcpy to copy the data. Array assignment like this is not allowed
You are trying to assign your variable address of a constant data,
try using
memcpy(graph->matrix,ADJ_MATRIX,sizeof(ADJ_MATRIX));//using sizeof(graph->matrix) is safer.
You can't use an array in assignments. You may use cycles or memcpy instead
memcpy(graph->matrix, ADJ_MATRIX, VERTEX_NUM * VERTEX_NUM * sizeof(int));
or
for(int i = 0; i < VERTEX_NUM; ++i){
for(int j = 0; j < VERTEX_NUM; ++j){
graph->matrix[i][j] = ADJ_MATRIX[i][j];
}
}
The error is thrown, because int matrix[VERTEX_NUM][VERTEX_NUM] in a structure definition means that each structure will have a 2D array of integers of the predefined size and matrix is going to be pointing to its first element. The thing is that matrix cannot be assigned to an arbitrary address, because it's a const pointer i.e. its value (the address it's pointing to) cannot change.
You have 2 options here: you can either use memcpy or some stl algorithms to copy the ADJ_MATRIX into matrix directly or you can declare matrix as a pointer and do the assignment that is currently produces an error.
The latter can be done in the following way:
typedef struct {
int vertex;
const int (*matrix)[VERTEX_NUM];
int vNum;
int eNum;
}Graph;
Thus you can do graph->matrix = ADJ_MATRIX assignment, but you won't be able to modify the individual items in matrix due to constness. This means, graph->matrix[0][1] = 3; is not allowed, while you can read the elements freely.
I'm pretty sure this is possible, because I'm pretty sure I've seen it done. I think it is awesome, but I will gladly accept answers along the lines of "this is a terrible idea because ____".
Say we have a basic struct.
struct vertex
{
float x, y, z;
};
Now, I want to implement aliases on these variables.
vertex pos;
vertex col;
vertex arr;
pos.x = 0.0f; pos.y = 0.5f; pos.z = 1.0f;
col.r = 0.0f; col.g = 0.5f; col.b = 1.0f;
arr[0] = 0.0f; arr[1] = 0.5f; arr[2] = 1.0f;
Ideally the third syntax would be indistinguishable from an array. That is, if I sent arr as a reference parameter to a function expecting an array of floats into which it will store data (eg many of the OpenGL glGet functions), it would work fine.
What do you think? Possible? Possible but stupid?
What I would do is make accessors:
struct Vertex {
float& r() { return values[0]; }
float& g() { return values[1]; }
float& b() { return values[2]; }
float& x() { return values[0]; }
float& y() { return values[1]; }
float& z() { return values[2]; }
float operator [] (unsigned i) const { return this->values_[i]; }
float& operator [] (unsigned i) { return this->values_[i]; }
operator float*() const { return this->values_; }
private:
float[3] values_;
}
Nameless nested structs in a union are not standard C++. This, however, should work:
struct Vertex
{
private:
typedef float Vertex::* const vert[3];
static const vert v;
public:
typedef size_t size_type;
float x, y, z;
const float& operator[](size_type i) const {
return this->*v[i];
}
float& operator[](size_type i) {
return this->*v[i];
}
};
const Vertex::vert Vertex::v = {&Vertex::x, &Vertex::y, &Vertex::z};
EDIT: A little more information. The struct uses an array of 3 pointer-to-data-members to access the data in the overloaded [] operators.
The line "typedef float Vertex::* const vert" means that vert is a pointer to a float member of the Vertex struct. The [3] means that it's an array of 3 of these. In the overloaded operator[], this array is indexed and the pointer-to-data-member is dereferenced and the value returned.
Additionally, this method should work regardless of packing issues - the compiler is free to pad the Vertex structure however it likes and it'll still work just fine. An anonymous union will run into problems if the floats are packed differently.
Use a union?
union vertex
{
struct { float x, y, z; };
struct { float r, g, b; };
float arr[3];
};
I wouldn't recommend it - it will lead to confusion.
Added:
As noted by Adrian in his answer, this union with anonymous struct members is not supported by ISO C++. It works in GNU G++ (with complaints about not being supported when you turn on '-Wall -ansi -pedantic'). It is reminiscent of the pre-pre-standard C days (pre-K&R 1st Edn), when structure element names had to be unique across all structures, and you could use contracted notations to get to an offset within the structure, and you could use member names from other structure types - a form of anarchy. By the time I started using C (a long time ago, but post-K&R1), that was already historical usage.
The notation shown with anonymous union members (for the two structures) is supported by C11 (ISO/IEC 9899:2011), but not by earlier versions of the C standard. Section 9.5 of ISO/IEC 14882:2011 (C++11) provides for anonymous unions, but GNU g++ (4.9.1) does not accept the code shown with -pedantic, identifying "warning: ISO C++ prohibits anonymous structs [-Wpedantic]".
Since the idea will lead to confusion, I'm not particularly concerned that it isn't standard; I would not use the mechanism for this task (and I'd be leery of using anonymous structures in a union even if it was beneficial).
A concern was raised:
The three (x-y-z, r-g-b and the array) do not necessarily align.
It is a union with three elements; the three elements start at the same address. The first two are structures containing 3 float values. There's no inheritance and there are no virtual functions to give different layouts, etc. The structures will be laid out with the three elements contiguous (in practice, even if the standard permits padding). The array also starts at the same address, and subject to 'no padding' in the structures, the elements overlap the two structures. I really don't see that there would be a problem.
References?
template<typename T>
struct vertex {
vertex() :
r(data[0]), g(data[1]), b(data[2]),
x(data[0]), y(data[1]), z(data[2])
{
}
T *operator *() {
return data;
}
const T *operator *() const {
return data;
}
T data[3];
T &r, &g, &b;
T &x, &y, &z;
};
You can get this with a union as others have mentioned. Overloading color and position onto the same structure like this may not be a good idea ( for example, adding two colors usually means you want to saturate to 1.0, whereas adding vectors happens linearly ), but overlaying a float[] on top of them like that is perfectly fine and a well accepted means of interchanging data with GL/DirectX/etc.
I recommend you avoid referring to the same member by different aliases in the same function scope, though, because this will drive you into a nasty hardware stall called a load-hit-store. In particular, avoid this if you can:
vector foo;
foo.x = 1.0f;
return foo[0] + foo[1];
Following structure will have the requested behavior:
struct vertex
{
private:
float data[3];
public:
float &x, &y, &z;
float &r, &g, &b;
vertex() : x(data[0]), y(data[1]), z(data[2]), r(data[0]), g(data[1]), b(data[2]) {
}
float& operator [](int i) {
return data[i];
}
};
I guess you can do some macro magic to get what you want.
But that will look ugly. Why do you want to use same struct, vertex for 3 different types? Why can't you define class for color?
Also keep in mind that vertex and color are not same. If you change something to vertex, that will affect the color also, if you have the same class for both.
I am not sure whether I understood the question correctly. But it looks like you need to overload the operator[] to provide array like access to your struct/class. See the example mentioned here: Operator overloading
I have a template and two Vector classes below, one crazy, one sane. The template implements a simple fixed at compile time array of values. It is designed for subclassing and uses a protected array variable to avoid you having to jump through hoops to access the array. (Some folks might not like such a design. I say, if your subclasses are calling your overloaded operators, coupling might be a good idea.)
The crazy class allows you to have member variables called x, y, z and it acts like an array for calls to glGetFloatV. The sane one just has accessor functions x(), y(), z() and still works with glGetFloatV. You can use either class as a basis for other vector objects you might pass to the OpenGL library. Although the classes below are specific to points, you can obviously just do a search/replace to turn them into a rgb color classes.
The crazy class is crazy because the cost of the syntactic sugar vec.x instead of vec.x() is 3 reference variables. That could take up a lot of space in a large application. Use the simpler sane version.
template <typename T, int N>
class FixedVector {
protected:
T arr[N];
public:
FixedVector();
FixedVector(const T* a) {
for (int i = 0; i < N; ++i) {
arr[i] = a[i];
}
}
FixedVector(const T& other) {
for (int i = 0; i < N; ++i) {
arr[i] = other.arr[i];
}
}
FixedVector& operator=(const T& other) {
for (int i = 0; i < N; ++i) {
arr[i] = other.arr[i];
}
return *this;
}
T* operator&() { return arr; }
const T* operator&() const { return arr; }
T& operator[](int ofs) {
assert(ofs >= 0 && ofs < N);
return arr[ofs];
}
const T& operator[](int ofs) const {
assert(ofs >= 0 && ofs < N);
return arr[ofs];
}
};
class CrazyPoint : public FixedVector<float, 3> {
public:
float &x, &y, &z;
CrazyPoint()
: x(arr[0]), y(arr[1]), z(arr[2])
{ arr[0] = arr[1] = arr[2] = 0.0; }
CrazyPoint(const float* a)
: x(arr[0]), y(arr[1]), z(arr[2])
{
arr[0] = a[0];
arr[1] = a[1];
arr[2] = a[2];
}
CrazyPoint(float a, float b, float c)
: x(a), y(b), z(c)
{
arr[0] = a;
arr[1] = b;
arr[2] = c;
}
};
class SanePoint : public FixedVector<float, 3> {
public:
float& x() { return arr[0]; }
float& y() { return arr[1]; }
float& z() { return arr[2]; }
SanePoint() { arr[0] = arr[1] = arr[2] = 0.0; }
SanePoint(float a, float b, float c)
{
arr[0] = a;
arr[1] = b;
arr[2] = c;
}
};
// usage
SanePoint normal;
glGetFloatV(GL_CURRENT_NORMAL, &normal);
Bad idea in my opinion, at least for the example given: the downside is that, for just about any solution to this, you're probably going to be able to freely assign "rgb" instances to/from "xyz" instances, which is probably rarely sensible or correct. ie you risk giving up some useful type safety.
Personally, for the example you give, I'd subclass rgb and xyz types from a base boost::array<float,3> or similar. So both of them inherit operator[], can be passed to functions expecting arrays, and passed with more type safety to things expecting colours/coordinates. It's often you want to treat an xyz or an rgb as an array, but rare you want to treat an xyz as an rgb or vice-versa. (rgb IS-A array: OK. xyz IS-A array: OK. rgb IS-A xyz ???? I don't think so!)
Of course that means access to x,y,z & r,g,b needs to be by accessor (forwarding to the appropriate operator[](...) ) rather than direct to the member. (You'd need C#'s properties for that).
You can try adding references to variables, like this:
struct test {
float x, y, z;
float &r, &g, &b;
test() : r(x), g(y), b(z) {}
};
But your structure gets bigger (from 12 bytes to 40 bytes).
To use [] on it, use overloading of operator[], as mentioned before.
Just a warning about using reference members pointing to value members. You need to define a copy constructor (and possibly also assignment operator), if you ever copy such an object (like transfer it by value). The default copy constructor will leave you with a copy whose reference members point to the value members of the original object, not the ones of the new object. This is certainly not something you want.
Considering you also end up with larger objects, as already pointed out, I think using accessor methods is to be preferred over reference members.
I think the poster was looking for something very simple, with no performance overhead - as you would want with something like a 3D vector class. So adding virtual functions (vtable indirection cost), additional members (memory cost), unions (each new name may require a recompile of all users), or even preprocessor magic (increased program size, cross-type equivalency) is undesirable.
Real world use case would be to take a templated Vector3 class (which could be double-based or float-based) and apply it to other scenarios in a user-friendly manner. It is likely defined with [x, y, z] members, but if you want to use it for rotations, you might want [psi, theta, phi], for speeds [dx, dy, dz], etc.
For the entire type, you can use the following to alias it at compile time:
using Rotation3 = Vector3;
But there appears to be nothing as simple or performant that you can do for aliasing the underlying variables, right?