1

Brushing up knowledge regarding (multidimensional) arrays / pointers conversions, the following two rules can explain some illegal conversions:

  1. An T[M][N] decays to a T(*)[N], but not to a T** (as is explained in this SO entry)
  2. There is not implicit conversion from T** to const T** (as is explained in the C++ faq)

So here is some test code written to try to cover different cases. The 3 cases we cannot explain are annotated with P1, P2 and P3.

int main() {
    {
        int arrayOfInt[3] = {0, 1, 2};
        int * toPtr{nullptr};
        int ** toPtrPtr{nullptr};
        const int ** toPtrPtrConst{nullptr};


        toPtr = arrayOfInt;

        //toPtrPtr = &arrayOfInt; //KO, I assume because of 1.
        //toPtrPtr = static_cast<int**>(&arrayOfInt); //KO, same as above
        toPtrPtr = reinterpret_cast<int**>(&arrayOfInt);
        toPtrPtr = (int**)&arrayOfInt;

        //toPtrPtrConst = &arrayOfInt; //KO, still 1.
        //toPtrPtrConst = static_cast<const int**>(&arrayOfInt); //KO, still 1.
        toPtrPtrConst = reinterpret_cast<const int**>(&arrayOfInt); // (P1)
            // it is supposed to be allowed to cast int** to const int* const*
            // not const int**
            // Why is it working without requiring a const_cast
            // to cast away the const qualifier?
        toPtrPtrConst = (const int**)&arrayOfInt;

        //toPtrPtrConst = toPtrPtr; //KO, because of 2.
        //toPtrPtrConst = reinterpret_cast<const int**>(toPtrPtr); //KO because of 2.
            // so why is P1 allowed?
    }

    {
        const int arrayOfConstInt[3] = {0, 1, 2};
        const int * toPtrConst{nullptr};
        const int ** toPtrPtrConst{nullptr};
        int * const * toPtrConstPtr{nullptr};

        toPtrConst = arrayOfConstInt;

        //toPtrPtrConst = &arrayOfConstInt; //KO, I assume because of 1.
        //toPtrPtrConst = static_cast<const int**>(&arrayOfConstInt); //KO, same as above
        //toPtrPtrConst = reinterpret_cast<const int**>(&arrayOfConstInt); // (P2) 
            // Compiler error "casts away qualifiers",
            // but which qualifier(s) would that cast away?
        toPtrPtrConst = (const int**)&arrayOfConstInt;

        //toPtrConstPtr = &arrayOfConstInt; //KO, I assume because of 1.
        //toPtrConstPtr = static_cast<int * const *>(&arrayOfConstInt); //KO, same as above
        toPtrConstPtr = reinterpret_cast<int * const *>(&arrayOfConstInt); // (P3) 
            // This one actually drops the const qualifier on the integer,
            // but nevertheless it compiles
        toPtrConstPtr = (int * const *)&arrayOfConstInt;

        toPtrConstPtr = reinterpret_cast<int * const *>(&toPtrConst); // KO
            // because it casts away const qualifier
            // so why is P3 allowed?
    }
}

Here it is in ideone: http://ideone.com/JzWmAJ

  • Why P1 allowed while it seems to violate 2.?
  • What are the qualifier(s) cast away by P2?
  • Why is P3 allowed when it actually casts away a const qualifier?
Ad N
  • 7,930
  • 6
  • 36
  • 80
  • Doesn't CFDictionaryCreate expect an array of pointers (or pointer sized objects)? –  Aug 02 '17 at 09:22
  • There is no way whatsoever to get an *array of pointer sized keys* as CFDictionaryCreate requires from an array of ints. You cannot conjure up an array of something in a place where there's an array of something completely different. This is a basic fact you need to know about arrays and pointers. So this exercise might be fascinating and all, but it has nothing to do with the problem at hand. You need to allocate an actual array of pointers, plain and simple. – n. m. could be an AI Aug 02 '17 at 10:08
  • The example was not to produce arguments related to CFDictionaryCreate, I removed this bit of context from the question as it seemed to carry the discussion away. – Ad N Aug 02 '17 at 14:01

3 Answers3

2
  1. An explicit type conversion (a.k.a. "cast") doesn't violate a rule about implicit type conversions because rules about implicit type conversions are, to everyone's lack of surprise, not applicable to explicit type conversions.
  2. const int (*)[3] is a type of a pointer-to-constant-something (where "something" is int[3]), whereas const int** is a type of a pointer-to-non-constant-something (where "something" is a pointer to a const int). The constant in pointer-to-constant-something gets stripped, which is not allowed.
  3. No qualifier is stripped. toPtrConstPtr is a pointer-to-constant-something (where something is int *) and &arrayOfConstInt is a pointer-to-constant-something (where something is int[3]).

It should be noted that this is strictly language lawyer material. No normal programmer should allow any of these cast anywhere near their code.

n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
  • Thank you for those explanations. Regarding your point #2, my belief was that, similar to the rule in C, there was no such thing as a _const array_ in C++, but only arrays to const elements. Is not that the case? – Ad N Aug 04 '17 at 06:04
  • Arrays essentially inherit constness from their element type: *Any type of the form “cv-qualifier-seq array of N T” is adjusted to “array of N cv-qualifier-seq T* and *An “array of N cv-qualifier-seq T” has cv-qualified type* (from 11.3.4/1) – n. m. could be an AI Aug 04 '17 at 07:01
1

All the casts in the code are bad, including all the ones that you didn't mark as bad.

If you do this:

cout << (uintptr_t)arrayOfInt << "\n";
cout << (uintptr_t)toPtrPtr << "\n";

you'll find that the output is identical. This is because &arrayOfInt has type int (*)[3]. When you dereference it with *&arrayOfInt, you get an array, which will decay back to a pointer of type int* with the same binary value as the pointer which you dereferenced. However, when you dereference an int**, you will load some bits from memory, and those bits will be the dereferenced pointer. These two dereferences are fundamentally incompatible. There is really no pretending that an int (*)[3] is the same as an int**.

cmaster - reinstate monica
  • 38,891
  • 9
  • 62
  • 106
0

T[N] are sometimes treated as a single type, as you use template-classes like std::array<T, N> or std::tuple<int, char, std::string>, and so it is treated like a single variable. Eg. jmp_buf of <csetjmp> is an array, but it is used like a normal variable. So while you are using fixed arrays in T[M][N], it is one linear block of N*M elements of type T as you write a member for each element in one struct, while pointer to pointer to T needs multiple memory-blocks, one memory-block for the pointer to the elements and the memory for each array pointed by the pointers. However arrays without fixed boundaries like T[] are treated like T*. When you use const T in an array, it works like a different type as T itsself, so you can c-cast an array of any type and if it is const or not, how you can cast char* to int*, c++ is strict and checks for const. The dereferenced arrayOfInt is an r-value, and reinterpret_cast sometimes has problems with r-values. Storing the dereferenced variable in a seperate variable and then reinterpret_cast it with the same type as a reference should work.

cmdLP
  • 1,658
  • 9
  • 19