32

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++.

NoahR
  • 1,417
  • 3
  • 18
  • 33
  • 2
    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: http://ideone.com/gytw7 – R. Martinho Fernandes Aug 08 '12 at 16:38
  • 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. – KRyan Aug 08 '12 at 16:48
  • 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? – NoahR Aug 08 '12 at 16:50
  • 2
    @DragoonWraith: Sorry, but you are wrong. `float a[2][2];` is not compatible with `float**`. – Ben Voigt Aug 08 '12 at 17:03
  • @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. – KRyan Aug 08 '12 at 17:17
  • 4
    @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. – Ben Voigt Aug 08 '12 at 17:20
  • @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) – KRyan Aug 08 '12 at 17:30
  • Related: [Convert pointer to two dimensional array](https://stackoverflow.com/q/41701239) / [Create a pointer to two-dimensional array](https://stackoverflow.com/q/1052818) – Peter Cordes Jan 04 '22 at 12:26

4 Answers4

45
float (*somethingAsMatrix)[2] = (float (*)[2]) matrixReturnAsArray;
ecatmur
  • 152,476
  • 27
  • 293
  • 366
  • 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]; – NoahR Aug 08 '12 at 16:51
  • 2
    @NoahR: That's contiguous, and compatible with the usage you suggest. – Ben Voigt Aug 08 '12 at 17:05
  • 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. – Jason Jan 22 '15 at 03:05
  • @Jason 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. – ecatmur Jan 22 '15 at 09:47
  • 1
    Why does this work ? Is there a clear documentation about this cast and the types mixing pointers and arrays notation ? Thx – Hugo Nov 30 '16 at 09:50
  • @ZachB I am talking about the right term of the equality (matricReturnAsArray) not the left term which is clearly a declaration. – Hugo Feb 12 '17 at 20:54
  • @Hugo 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-name*s, 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]`.) – ecatmur Feb 13 '17 at 13:52
  • Thanks @ecatmur 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 ? – Hugo Feb 15 '17 at 16:18
  • @Hugo yes, that's right - [cdecl.org](http://cdecl.org/?q=float+%28*identifier%29%5B2%5D) says "declare identifier as pointer to array 2 of float". – ecatmur Feb 16 '17 at 16:52
8

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) {}
bames53
  • 86,085
  • 15
  • 179
  • 244
  • 1
    Isn't `sizeof (T [N])` guaranteed to be exactly `N * sizeof (T)`? – Ben Voigt Aug 08 '12 at 18:11
  • 1
    I'm aware of a non-normative note in the standard that mentions that. If you can find anything normative please let me know. – bames53 Aug 08 '12 at 18:12
  • 1
    5.3.3p2 directly states that it is required. Looks normative to me (not inside a "Note") – Ben Voigt Aug 08 '12 at 18:13
  • "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. – bames53 Aug 08 '12 at 18:20
  • relevant: https://groups.google.com/forum/?fromgroups#!topic/comp.lang.c++/jpCa0W_wLdI%5B1-25%5D – Ben Voigt Aug 08 '12 at 19:21
  • 1
    If nothing else, that line prohibits padding at the end of an array, because that line is normative. – Ben Voigt Aug 08 '12 at 19:23
  • 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. – bames53 Aug 08 '12 at 19:37
1

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.)

Gort the Robot
  • 2,329
  • 16
  • 21
  • 1
    Use someThingAsMatrix[1][1] = 2.0f; gives: Incompatible types in assignment of 'float' to 'float [2]' – NoahR Aug 08 '12 at 17:01
  • 3
    Typedef is still helpful, but you need: `typedef float MatrixRow[2]; MatrixRow* someThingAsMatrix = (MatrixRow*) matrixReturnAsArray;` which is equivalent to ecatmur's answer. – Ben Voigt Aug 08 '12 at 17:07
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] = ...

cerv
  • 1