How to cast simple pointer to a multidimensional-array of fixed size?
Asked Answered
H

4

34

I have a function that takes a pointer to a floating point array. Based on other conditions, I know that pointer is actually pointing to a 2x2 OR 3x3 matrix. (in fact the memory was initially allocated as such, e.g. float M[2][2] ) The important thing is I want to make this determination in the function body, not as the function argument.

void calcMatrix( int face, float * matrixReturnAsArray )
{
    // Here, I would much rather work in natural matrix notation
    if( is2x2 )
    {
        // ### cast matrixReturnAsArray to somethingAsMatrix[2][2]
        somethingAsMatrix[0][1] = 2.002;
        // etc..
    }
    else if(is3x3)
    { //etc...
    }

}

I am aware that I could use templates and other techniques to better address this problem. My question is really about how to make such a cast at the ### comment. Working in C++.

Hinterland answered 8/8, 2012 at 16:33 Comment(8)
I think you're trying to solve the wrong problem (i.e. "how to cast?" is rarely the right problem). I wrote a simple solution to the problem of "how to use multi-dimensional arrays easily?" once: ideone.com/gytw7Luht
There is no way a float * points to a multidimensional anything (barring really terrible casting that you shouldn't be doing and I'd be surprised if a compiler let you). A float * points to a float, which might be the first value in a single-dimensional float array. But it does not point to any sub-array, as you would need for a multidimensional array. 2x2 and 3x3 are both 2D, so both could be a float **. Really, though, you'd be much better off creating (or finding) and using a dedicated Matrix class.October
Ok, I could change my input argument to float **. But are you saying that in the case float aMDarray[3][3], the storage of elements is not guaranteed to be continuous?Hinterland
@DragoonWraith: Sorry, but you are wrong. float a[2][2]; is not compatible with float**.Rana
@BenVoigt: Yeah, you're right; they're not and I misspoke. I meant more that float * cannot be more than unidimensional and float ** is needed for two dimensions.October
@DragoonWraith: That's still wrong, though. float a[2][2]; is a still a single sequence of float stored contiguously, but with compiler-provided two-dimensional address calculation. You can then write float* p = &a[0][0]; and do the index calculation yourself.Rana
@BenVoigt: Ah, then I simply misunderstood entirely. My apologies. Thanks for the clarification, +1 on both. (Is it better to delete the comment, or not? Not sure of SO-tiquette here)October
Related: Convert pointer to two dimensional array / Create a pointer to two-dimensional arrayJopa
F
47
float (*somethingAsMatrix)[2] = (float (*)[2]) matrixReturnAsArray;
Fabiolafabiolas answered 8/8, 2012 at 16:41 Comment(9)
Thank you. This certainly works now. I'm just clarifying if I have a misconception about the guaranteed contiguous arrangement of elements in the declaration case float aMDarray[3][3];Hinterland
@NoahR: That's contiguous, and compatible with the usage you suggest.Rana
I tried this technique and the Mac OS X C++ compiler said error: assigning to 'double (*)[LDN]' from incompatible type 'double (*)[LDN]'. Notice the two reported types are the same! So why are they not assignable? My code looks like this double (*F)[LDN]; F=(double (*)[LDN])AF; I also tried initializing in the declaration and got a similar error message.Kolva
@Kolva perhaps your compiler is broken? If you can provide the full code that you're having trouble with then it'd be worth asking a separate question.Fabiolafabiolas
Why does this work ? Is there a clear documentation about this cast and the types mixing pointers and arrays notation ? ThxSmolt
@ZachB I am talking about the right term of the equality (matricReturnAsArray) not the left term which is clearly a declaration.Smolt
@Smolt this is documented in the C Standard (ISO/IEC 9899:2011): 6.5.4 says that a cast-expression is a unary-expression preceded by any number of parenthesized type-names, and 6.7.7 says that a type-name is "syntactically a declaration for a function or an object of that type that omits the identifier". (So from float (*somethingAsMatrix)[2] we can remove the identifier somethingAsMatrix to get the type-name float (*)[2].)Fabiolafabiolas
Thanks @Fabiolafabiolas that is very clear. Nevertheless my next question is : what do you call this type-name float (*identifier)[2] ? Is it a pointer to 2-sized-arrays of float ie a pointer to a space full of 2-sized-float-arrays ?Smolt
@Smolt yes, that's right - cdecl.org says "declare identifier as pointer to array 2 of float".Fabiolafabiolas
D
9

float * could point to the first element of an array of floats, and ought to be reinterpret_castable to that array type. And the result of that cast could point to the first element of a float [][] and so should be reinterpret_castable to that type, and so on. You ought to be able to compose such casts and just directly do

float (&arr)[2][2] = *reinterpret_cast<float (*)[2][2]>(matrixReturnAsArray);

An argument of the type float ** is not the same and should not be used this way.

To avoid undefined behavior the pointer must originate from an actual multi-dimensional array, and if the float* is used directly you cannot access more than the first row of the multi-dimensional matrix.

void foo(float *f) {
    f[3] = 10.;

    float (&arr)[2][2] = *reinterpret_cast<float (*)[2][2]>(f);
    arr[1][1] = 10.;
}

void main() {
    float a[2][2];
    foo(&a[0][0]); // f[3] = 10.; is undefined behavior, arr[1][1] = 10. is well defined

    float b[4];
    foo(&b[0]); // f[3] = 10.; is well-defined behavior, arr[1][1] = 10. is undefined
}

Given float arr[2][2]; nothing guarantees that &arr[0][1] + 1 is the same as &arr[1][0], as far as I have been able to determine. So although you can use a single dimensional array as a multi-dimensional array by doing f[i*width + j] you cannot treat a multi-dimensional array like a single dimensional array.

It's better to use C++'s compile-time type-safety instead of just relying on not accidentally passing the wrong thing or performing the wrong reinterpret_cast. To get type-safety using raw-arrays you should use references to the raw array type you want:

void foo(float (&f)[2][2]) {}
void foo(float (&f)[3][3]) {}

If you want to pass arrays by value you can't use raw arrays and should instead use something like std::array:

void foo(std::array<std::array<float,2>,2> f) {}
void foo(std::array<std::array<float,3>,3> f) {}
Dilettante answered 8/8, 2012 at 17:4 Comment(7)
Isn't sizeof (T [N]) guaranteed to be exactly N * sizeof (T)?Rana
I'm aware of a non-normative note in the standard that mentions that. If you can find anything normative please let me know.Dilettante
5.3.3p2 directly states that it is required. Looks normative to me (not inside a "Note")Rana
"When applied to an array, the result is the total number of bytes in the array. This implies that the size of an array of n elements is n times the size of an element." The issue is that I don't see anything that actually prohibits an array from containing extra space at the end. It's said to be 'implied' but I don't see how.Dilettante
relevant: groups.google.com/forum/?fromgroups#!topic/comp.lang.c++/…Rana
If nothing else, that line prohibits padding at the end of an array, because that line is normative.Rana
If there's nothing else that actually implies what that line says is implied then I would not regard it as a normative statement, I would regard it only as a factually incorrect statement and a bug in the standard.Dilettante
Y
2

This sort of casting is always cleaner, and easier to deal with, with a judicious use of typedef:

typedef float Matrix_t[2][2];

Matrix_t* someThingAsMatrix = (Matrix_t*) matrixReturnAsArray;

If this is C++ and not C, though, you should create a matrix class. (Or better yet, look for an open source one.)

Yellowhammer answered 8/8, 2012 at 16:50 Comment(2)
Use someThingAsMatrix[1][1] = 2.0f; gives: Incompatible types in assignment of 'float' to 'float [2]'Hinterland
Typedef is still helpful, but you need: typedef float MatrixRow[2]; MatrixRow* someThingAsMatrix = (MatrixRow*) matrixReturnAsArray; which is equivalent to ecatmur's answer.Rana
K
0

If I am right:

typedef float Matrix_t[2][2];  
Matrix_t &matrix = *(Matrix_t *)matrixReturnAsArray; 

or

float (&matrix2)[2][2] = *(float ( *)[2][2])matrixReturnAsArray;  

In C there is only the way with the pointer

Matrix_t *someThingAsMatrix = (Matrix_t *)matrixReturnAsArray;  

and access via:

(*someThingAsMatrix)[1][0] = ...  
Knuth answered 28/6, 2022 at 15:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.