NSDictionary == NSMutableDictionary #pragma


Gary L. Wade
 

It appears the responder was caught up in the particulars of your example, so I’d suggest having an example that’s more “real world” to avoid confusion. Similar code in Swift produces an error so be sure to mention that.
--
Gary

On Jun 27, 2022, at 11:44 AM, Ben Kennedy <ben-groups@...> wrote:

To my shock and surprise, I received a response from Apple on Friday, a mere 11 days after filing it (FB10228325) !

For reference, my report included the following code (lifted from the original thread):

@import Foundation;

void foo(NSMutableDictionary** mutableDictionary)
{
(*mutableDictionary)[@"hello"] = @"world";
}

int main(int argc, const char * argv[])
{
NSDictionary* dictionary = [[NSDictionary alloc] init];
foo(&dictionary); // no compilation error here!
return 0;
}
The Apple reply is as follows:

Is it necessary for foo to take parameter NSMutableDictionary**? Can you change it to NSMutableDictionary*`?

void foo(NSMutableDictionary* mutableDictionary)
{
mutableDictionary[@"hello"] = @"world";
}

We don’t think clang should reject the code just because it is passing a NSDictionary** to a function expecting a NSMutableDictionary**. We don’t want to reject the following code, for example:

void foo(NSMutableDictionary** mutableDictionary)
{
NSMutableDictionary* md = [[NSMutableDictionary alloc] init];
*mutableDictionary = md;
}

foo is assigning a pointer to a newly created NSMutableDictionary object to dictionary, which is an NSDictionary pointer, in main.
I don't think I understand the reasoning offered by the Apple respondent, but before I reply, I wanted to see if it's meaningful to anyone else.

Whether or not foo() takes a double indirection, and despite what it might do in its body, I don't see how the suggested change remedies the error of passing an immutable dictionary reference at the call site in main().

-ben


Keary Suska
 

It seems to me that the point being made is that the reason for a method requesting a pointer to an object is that the method will set what is being pointed to, and not that the method will attempt to manipulate the object pointed to. If the latter was the case, requesting the object directly is the better choice. Further, from the caller’s perspective, the object pointed to will behave exactly as expected, as that is the inheritance contract.

If the intent of passing the indirection was to provide an object for manipulation, well, that is problematic coding on multiple levels, and the compiler can’t protect the programmer from all of that.

Not that I am arguing that the issue shouldn’t be flagged, as canonically it really should be, but I do see their point as to whether it is worth the effort to do so.

Keary Suska
Esoteritech, Inc.
"Demystifying technology for your home or business”

On Jun 27, 2022, at 12:44 PM, Ben Kennedy <ben-groups@...> wrote:

To my shock and surprise, I received a response from Apple on Friday, a mere 11 days after filing it (FB10228325) !

For reference, my report included the following code (lifted from the original thread):

@import Foundation;

void foo(NSMutableDictionary** mutableDictionary)
{
(*mutableDictionary)[@"hello"] = @"world";
}

int main(int argc, const char * argv[])
{
NSDictionary* dictionary = [[NSDictionary alloc] init];
foo(&dictionary); // no compilation error here!
return 0;
}
The Apple reply is as follows:

Is it necessary for foo to take parameter NSMutableDictionary**? Can you change it to NSMutableDictionary*`?

void foo(NSMutableDictionary* mutableDictionary)
{
mutableDictionary[@"hello"] = @"world";
}

We don’t think clang should reject the code just because it is passing a NSDictionary** to a function expecting a NSMutableDictionary**. We don’t want to reject the following code, for example:

void foo(NSMutableDictionary** mutableDictionary)
{
NSMutableDictionary* md = [[NSMutableDictionary alloc] init];
*mutableDictionary = md;
}

foo is assigning a pointer to a newly created NSMutableDictionary object to dictionary, which is an NSDictionary pointer, in main.
I don't think I understand the reasoning offered by the Apple respondent, but before I reply, I wanted to see if it's meaningful to anyone else.

Whether or not foo() takes a double indirection, and despite what it might do in its body, I don't see how the suggested change remedies the error of passing an immutable dictionary reference at the call site in main().

-ben






Peter Teeson
 

I am not sure if this is a relevant example but this function, to get all the keys and values from a dictionary for you,
requires parameters for the keys and values to be
 ...C arrays of pointer-sized elements to be filled with keys and values from the dictionary"
void CFDictionaryGetKeysAndValues(CFDictionaryRef theDict, const void **keys, const void **values);

hence
        const void **keys[dictSize];   
        const void **values[dictSize];
 CFDictionaryGetKeysAndValues(dictRef,*keys, *values);


On Jun 28, 2022, at 9:38 AM, Keary Suska <cocoa-dev@...> wrote:

It seems to me that the point being made is that the reason for a method requesting a pointer to an object is that the method will set what is being pointed to, and not that the method will attempt to manipulate the object pointed to. If the latter was the case, requesting the object directly is the better choice. Further, from the caller’s perspective, the object pointed to will behave exactly as expected, as that is the inheritance contract.

If the intent of passing the indirection was to provide an object for manipulation, well, that is problematic coding on multiple levels, and the compiler can’t protect the programmer from all of that.

Not that I am arguing that the issue shouldn’t be flagged, as canonically it really should be, but I do see their point as to whether it is worth the effort to do so.

Keary Suska
Esoteritech, Inc.
"Demystifying technology for your home or business”



On Jun 27, 2022, at 12:44 PM, Ben Kennedy <ben-groups@...> wrote:

To my shock and surprise, I received a response from Apple on Friday, a mere 11 days after filing it (FB10228325) !

For reference, my report included the following code (lifted from the original thread):

@import Foundation;

void foo(NSMutableDictionary** mutableDictionary)
{
  (*mutableDictionary)[@"hello"] = @"world";
}

int main(int argc, const char * argv[])
{
  NSDictionary* dictionary = [[NSDictionary alloc] init];
  foo(&dictionary); // no compilation error here!
  return 0;
}

The Apple reply is as follows:

Is it necessary for foo to take parameter NSMutableDictionary**? Can you change it to NSMutableDictionary*`?

void foo(NSMutableDictionary* mutableDictionary)
{
  mutableDictionary[@"hello"] = @"world";
}

We don’t think clang should reject the code just because it is passing a NSDictionary** to a function expecting a NSMutableDictionary**. We don’t want to reject the following code, for example:

void foo(NSMutableDictionary** mutableDictionary)
{
  NSMutableDictionary* md = [[NSMutableDictionary alloc] init];
  *mutableDictionary = md;
}

foo is assigning a pointer to a newly created NSMutableDictionary object to dictionary, which is an NSDictionary pointer, in main.

I don't think I understand the reasoning offered by the Apple respondent, but before I reply, I wanted to see if it's meaningful to anyone else.

Whether or not foo() takes a double indirection, and despite what it might do in its body, I don't see how the suggested change remedies the error of passing an immutable dictionary reference at the call site in main().

-ben














Alex Zavatone
 

While the response has focused on a different issue, I think that the argument from your side should be, “I understand your question, but mine is simply, shouldn’t the compiler catch this and at least warn against it?”

And then spell out why that it should. What’s going through the reviewer’s head isn’t what has been going through yours when you wrote this up.

You want to make what’s obvious to you, painfully obvious to them.

“Sure, I could do xxx and yyy, but based on ZZZZ, this statement clearly is not defined to support a pointer to a pointer. It’s just defined to support a pointer. Shouldn’t the compiler catch this and warn against this unintended use that causes a crash at runtime?”

I’m sure it could be reworded to be clearer, but how does that sound as a start?

Cheers,
Alex Zavatone

On Jun 27, 2022, at 1:44 PM, Ben Kennedy <ben-groups@...> wrote:

To my shock and surprise, I received a response from Apple on Friday, a mere 11 days after filing it (FB10228325) !

For reference, my report included the following code (lifted from the original thread):

@import Foundation;

void foo(NSMutableDictionary** mutableDictionary)
{
(*mutableDictionary)[@"hello"] = @"world";
}

int main(int argc, const char * argv[])
{
NSDictionary* dictionary = [[NSDictionary alloc] init];
foo(&dictionary); // no compilation error here!
return 0;
}
The Apple reply is as follows:

Is it necessary for foo to take parameter NSMutableDictionary**? Can you change it to NSMutableDictionary*`?

void foo(NSMutableDictionary* mutableDictionary)
{
mutableDictionary[@"hello"] = @"world";
}

We don’t think clang should reject the code just because it is passing a NSDictionary** to a function expecting a NSMutableDictionary**. We don’t want to reject the following code, for example:

void foo(NSMutableDictionary** mutableDictionary)
{
NSMutableDictionary* md = [[NSMutableDictionary alloc] init];
*mutableDictionary = md;
}

foo is assigning a pointer to a newly created NSMutableDictionary object to dictionary, which is an NSDictionary pointer, in main.
I don't think I understand the reasoning offered by the Apple respondent, but before I reply, I wanted to see if it's meaningful to anyone else.

Whether or not foo() takes a double indirection, and despite what it might do in its body, I don't see how the suggested change remedies the error of passing an immutable dictionary reference at the call site in main().

-ben






 



On Jun 27, 2022, at 11:44 AM, Ben Kennedy <ben-groups@...> wrote:

I don't think I understand the reasoning offered by the Apple respondent, but before I reply, I wanted to see if it's meaningful to anyone else.

It took some puzzling, but I think I understand what they meant. I also think they’re wrong.

In the example you gave them, the foo function is taking an input parameter (with more indirection than necessary) and operating on it. Here the compiler’s behavior is wrong: you shouldn’t be able to pass an NSDictionary* through a parameter typed as NSMutableDictionary* (even if there’s an additional indirection.) This kind of parameter passing is covariant.

The example they gave is the opposite, using an “out” parameter. The function creates an NSDictionary and returns it through the indirected parameter. This case is valid. Returning values is contravariant.

Unfortunately the function signature is the same for both cases, so the compiler can’t tell which one is going to happen, at least not in the limiting case where ‘foo’ and its caller are in different compilation units or different dylibs. Therefore the compiler shouldn’t allow this, in either direction. (I.e. if you tried to pass an NSMutableDictionary through an NSDictionary** param it should fail too.)

I am pretty certain that if you tried this same example in C++ using a simple class hierarchy it would fail with an error. That might be a more convincing argument to them. 

[a few minutes pass] Yes, a trivial C++ example fails:

class A { };
class B : public A { };
void foo(B** b) { }
void bar(A** a) { }

int main(int argc, char *argv[]) {
A* a = new A;
foo(&a); // “no matching function for call to ‘foo’"

B* b = new B;
bar(&b); // “no matching function for call to ‘bar’"
}

—Jens


Keary Suska
 

I might be wrong here, but I thought that Objective-C’s typing and inheritance rules permit passing a subclass to a parameter typed to the superclass. That is, an NSMutableDictionary *should* be acceptable to pass to a parameter of type NSDictionary. After all, it *is* an NSDictionary. If this is the case, saying that it doesn't work that way in C++ is probably not a convincing argument.

Of course, maybe C++ is only strict about that with indirection, which makes a slightly better case, but should be spelled out in the example, and include the motivation for the compiler rule from an authoritative source.

Keary Suska
Esoteritech, Inc.
"Demystifying technology for your home or business"

On Jun 28, 2022, at 6:34 PM, Jens Alfke <jens@...> wrote:



On Jun 27, 2022, at 11:44 AM, Ben Kennedy <ben-groups@...> wrote:

I don't think I understand the reasoning offered by the Apple respondent, but before I reply, I wanted to see if it's meaningful to anyone else.
It took some puzzling, but I think I understand what they meant. I also think they’re wrong.

In the example you gave them, the foo function is taking an input parameter (with more indirection than necessary) and operating on it. Here the compiler’s behavior is wrong: you shouldn’t be able to pass an NSDictionary* through a parameter typed as NSMutableDictionary* (even if there’s an additional indirection.) This kind of parameter passing is covariant.

The example they gave is the opposite, using an “out” parameter. The function creates an NSDictionary and returns it through the indirected parameter. This case is valid. Returning values is contravariant.

Unfortunately the function signature is the same for both cases, so the compiler can’t tell which one is going to happen, at least not in the limiting case where ‘foo’ and its caller are in different compilation units or different dylibs. Therefore the compiler shouldn’t allow this, in either direction. (I.e. if you tried to pass an NSMutableDictionary through an NSDictionary** param it should fail too.)

I am pretty certain that if you tried this same example in C++ using a simple class hierarchy it would fail with an error. That might be a more convincing argument to them.

[a few minutes pass] Yes, a trivial C++ example fails:

class A { };
class B : public A { };
void foo(B** b) { }
void bar(A** a) { }

int main(int argc, char *argv[]) {
A* a = new A;
foo(&a); // “no matching function for call to ‘foo’"

B* b = new B;
bar(&b); // “no matching function for call to ‘bar’"
}

—Jens


davelist@...
 

On Jun 27, 2022, at 2:44 PM, Ben Kennedy <ben-groups@...> wrote:

To my shock and surprise, I received a response from Apple on Friday, a mere 11 days after filing it (FB10228325) !

For reference, my report included the following code (lifted from the original thread):

@import Foundation;

void foo(NSMutableDictionary** mutableDictionary)
{
(*mutableDictionary)[@"hello"] = @"world";
}

int main(int argc, const char * argv[])
{
NSDictionary* dictionary = [[NSDictionary alloc] init];
foo(&dictionary); // no compilation error here!
return 0;
}
The Apple reply is as follows:

Is it necessary for foo to take parameter NSMutableDictionary**? Can you change it to NSMutableDictionary*`?

void foo(NSMutableDictionary* mutableDictionary)
{
mutableDictionary[@"hello"] = @"world";
}

We don’t think clang should reject the code just because it is passing a NSDictionary** to a function expecting a NSMutableDictionary**. We don’t want to reject the following code, for example:

void foo(NSMutableDictionary** mutableDictionary)
{
NSMutableDictionary* md = [[NSMutableDictionary alloc] init];
*mutableDictionary = md;
}

foo is assigning a pointer to a newly created NSMutableDictionary object to dictionary, which is an NSDictionary pointer, in main.
I don't think I understand the reasoning offered by the Apple respondent, but before I reply, I wanted to see if it's meaningful to anyone else.

Whether or not foo() takes a double indirection, and despite what it might do in its body, I don't see how the suggested change remedies the error of passing an immutable dictionary reference at the call site in main().

-ben

I believe their point is that is legal to assign a (pointer to a) mutable dictionary to a (pointer to a) dictionary. In other words, dictionary = mutableDictionary is legal because a mutable dictionary "is-a" dictionary (obviously in Objective-C, you need to use pointers for all NSObject subclasses).

So in the their example, they don't want to flag this as an error because the end result is that a (pointer to a) mutable dictionary is assigned to a (pointer to a) dictionary, which is legal.

This does produce a warning about incompatible types so they're asking you to write it like this:

void foo(NSMutableDictionary* mutableDictionary)
{
mutableDictionary[@"hello"] = @"world";
}

int main(int argc, const char * argv[])
{
NSDictionary* dictionary = [[NSDictionary alloc] init];
foo(dictionary); // this produces a warning
return 0;
}

HTH,
Dave


Roland King
 

On 2022-06-30 09:14, davelist via groups.io wrote:
On Jun 27, 2022, at 2:44 PM, Ben Kennedy <ben-groups@...> wrote:
To my shock and surprise, I received a response from Apple on Friday, a mere 11 days after filing it (FB10228325) !
For reference, my report included the following code (lifted from the original thread):

@import Foundation;
void foo(NSMutableDictionary** mutableDictionary)
{
(*mutableDictionary)[@"hello"] = @"world";
}
int main(int argc, const char * argv[])
{
NSDictionary* dictionary = [[NSDictionary alloc] init];
foo(&dictionary); // no compilation error here!
return 0;
}
The Apple reply is as follows:

Is it necessary for foo to take parameter NSMutableDictionary**? Can you change it to NSMutableDictionary*`?
void foo(NSMutableDictionary* mutableDictionary)
{
mutableDictionary[@"hello"] = @"world";
}
We don’t think clang should reject the code just because it is passing a NSDictionary** to a function expecting a NSMutableDictionary**. We don’t want to reject the following code, for example:
void foo(NSMutableDictionary** mutableDictionary)
{
NSMutableDictionary* md = [[NSMutableDictionary alloc] init];
*mutableDictionary = md;
}
foo is assigning a pointer to a newly created NSMutableDictionary object to dictionary, which is an NSDictionary pointer, in main.
I don't think I understand the reasoning offered by the Apple respondent, but before I reply, I wanted to see if it's meaningful to anyone else.
Whether or not foo() takes a double indirection, and despite what it might do in its body, I don't see how the suggested change remedies the error of passing an immutable dictionary reference at the call site in main().
-ben
I believe their point is that is legal to assign a (pointer to a)
mutable dictionary to a (pointer to a) dictionary. In other words,
dictionary = mutableDictionary is legal because a mutable dictionary
"is-a" dictionary (obviously in Objective-C, you need to use pointers
for all NSObject subclasses).
So in the their example, they don't want to flag this as an error
because the end result is that a (pointer to a) mutable dictionary is
assigned to a (pointer to a) dictionary, which is legal.
This does produce a warning about incompatible types so they're asking
you to write it like this:
void foo(NSMutableDictionary* mutableDictionary)
{
mutableDictionary[@"hello"] = @"world";
}
int main(int argc, const char * argv[])
{
NSDictionary* dictionary = [[NSDictionary alloc] init];
foo(dictionary); // this produces a warning
return 0;
}
HTH,
Dave
yes it's legal to assign a pointer to a NSMutableDictionary to a pointer to an NSDictionary because an NSMutableDictionary IS an NSDictionary, ie everything you can do to an NSDictionary you can do to an NSMutableDictionary. However the original question was the other way around, it's about assigning a pointer to an NSDictionary to an NSMutableDictionary which I agree is wrong. It's clearly wrong from the example as foo() calls a mutating method on it which will then fail if the object happens to be an NSDictionary.

There's been some discussion about what C++ would do in this case. Very simple, if NSDictionary were a superclass of NSMutableDictionary, which in a C++ hierarchy it would usually be, then supplying NSMutableDictionary pointer or reference to a method which took an NSDictionary pointer or reference would succeed and supplying an NSDictionary pointer or reference to a method which took a mutable one would fail, at compilation time.

Of course this is objective-C which is a whole lot more duck-typed and generally dynamic so I'm not surprised it doesn't necessarily work that way.

Unless I've managed to trim the thread somewhere and am missing something Apple's reply makes no sense at all because it doesn't actually contain a NSDictionary in it at all and has NSMutableDictionary(s) which completely sidesteps the entire point of the question. I literally don't have a clue what they are trying to say.

My memory, admittedly dulled by not cocoa programming for about 5 years seems to recall that under the hood NSDictionary and NSMutableDictionary are not really parent-child objects but actually either one class with slightly different behaviour depending on how you construct it or two sibling classes which thus don't behave quite the way you might expect.


 



On Jun 29, 2022, at 7:28 AM, Keary Suska <cocoa-dev@...> wrote:

I might be wrong here, but I thought that Objective-C’s typing and inheritance rules permit passing a subclass to a parameter typed to the superclass.

Yes, any OO language supports that. It’s a basic principle of OOP.

That is, an NSMutableDictionary *should* be acceptable to pass to a parameter of type NSDictionary. After all, it *is* an NSDictionary. If this is the case, saying that it doesn't work that way in C++ is probably not a convincing argument.

Passing NSMutableDictionary* to an NSDictionary* parameter is OK.
Passing NSMutableDictionary** to an NSDictionary** parameter should not be OK (and is not in C++.)
See the difference?

The reason it’s not OK is that the double-indirection allows the caller to store a new object reference into the pointed-to pointer, and in this case it could store a reference to an immutable NSDictionary. On return, the caller would then have an NSMutableDictionary* that didn’t hold an instance of NSMutableDictionary, breaking strict typing.

BTW, if you changed the parameter type to NSDictionary* const*, the problem would go away, at least in C++. The ‘const’ prevents the callee from storing a new object reference.

—Jens


 



On Jun 29, 2022, at 6:44 PM, Roland King <rols@...> wrote:

There's been some discussion about what C++ would do in this case. Very simple, if NSDictionary were a superclass of NSMutableDictionary, which in a C++ hierarchy it would usually be, then supplying NSMutableDictionary pointer or reference to a method which took an NSDictionary pointer or reference would succeed

Argh. Am I the only person who noticed the double asterisks in the parameter types?
It’s NSDictionary** and NSMutableDictionary**
not NSDictionary* and NSMutableDictionary*
That makes a very large difference.

—Jens


Alex Zavatone
 



On Jul 1, 2022, at 4:32 PM, Jens Alfke <jens@...> wrote:



On Jun 29, 2022, at 6:44 PM, Roland King <rols@...> wrote:

There's been some discussion about what C++ would do in this case. Very simple, if NSDictionary were a superclass of NSMutableDictionary, which in a C++ hierarchy it would usually be, then supplying NSMutableDictionary pointer or reference to a method which took an NSDictionary pointer or reference would succeed

Argh. Am I the only person who noticed the double asterisks in the parameter types?
It’s NSDictionary** and NSMutableDictionary**
not NSDictionary* and NSMutableDictionary*
That makes a very large difference.

Yeah, that’s the big deal here.




Keary Suska
 

I for one, did notice. I was simply taking for granted that manipulating an object passed by indirection is itself a bad coding practice and should be avoided at all costs, whether or not one is the author of the called code. The method should just request the object “directly” so the intent is clear. The use of indirection is pointless unless it is an out parameter (or an array of C strings, but that’s something altogether different). If it is both, then my argument stands, as the intent in unclear and is a bad code smell to boot.

As an out parameter, it is only problematic if the caller passes a subclass for a parent class. The caller might then be expecting the object to behave differently and errors could ensue. The reverse would be error free, which was my point that you missed.

So back to the complaint at hand—since the compiler may not be able to know the intent of the method, flagging the type mismatch may be a better thing to do, but it is also just the pedantic thing to do, and although technically correct, is it worth it to the compiler developers to implement it? The use cases for requiring flagging are outside of convention and involve edge cases. Should a compiler flag every possible stupid thing a programmer could do? The list would be endless! No, they pick and choose their battles, and hope that language conventions would prevent implementors from doing things, like, manipulating a parameter passed by indirection.

Keary Suska
Esoteritech, Inc.
"Demystifying technology for your home or business"

On Jul 1, 2022, at 3:32 PM, Jens Alfke <jens@...> wrote:



On Jun 29, 2022, at 6:44 PM, Roland King <rols@...> wrote:

There's been some discussion about what C++ would do in this case. Very simple, if NSDictionary were a superclass of NSMutableDictionary, which in a C++ hierarchy it would usually be, then supplying NSMutableDictionary pointer or reference to a method which took an NSDictionary pointer or reference would succeed
Argh. Am I the only person who noticed the double asterisks in the parameter types?
It’s NSDictionary** and NSMutableDictionary**
not NSDictionary* and NSMutableDictionary*
That makes a very large difference.

—Jens


 



On Jul 1, 2022, at 9:17 PM, Keary Suska <cocoa-dev@...> wrote:

The use of indirection is pointless unless it is an out parameter (or an array of C strings, but that’s something altogether different). If it is both, then my argument stands, as the intent in unclear and is a bad code smell to boot.

“inout” parameters are a valid thing too, but yes, not commonly used.

So back to the complaint at hand—since the compiler may not be able to know the intent of the method, flagging the type mismatch may be a better thing to do, but it is also just the pedantic thing to do, and although technically correct, is it worth it to the compiler developers to implement it? The use cases for requiring flagging are outside of convention and involve edge cases. Should a compiler flag every possible stupid thing a programmer could do? 

Type systems are strict, and yes, compilers generally aim to flag any violation as an error. Type systems are all about providing guarantees of [a limited domain of] safety, and a hole like this isn’t something I’d expect a compiler to leave open intentionally.

(That doesn’t extend to language features that let the programmer explicitly avoid or subvert the type system, like ‘id’ or type-casts.)

I do concede that the safety violation is less severe in Obj-C than in many other typed languages like C++, since message-sends are late-bound; and thus calling a method on the wrong type of receiver will result in well-defined (if unpleasant) behavior like an exception, instead of C++’s undefined behavior when you dispatch through the wrong vtable, which is Very Bad Indeed.

—Jens


Peter Teeson
 

I noticed and sent this example of Apple’s code using CFDictionary… note the double asterisks for the keys and values parms. 
hence an array of dictSize pointers, *keys[dictSize], and type const void *. No copmilation complaint.

I am not sure if this is a relevant example but this function, to get all the keys and values from a dictionary for you,
requires parameters for the keys and values to be

 ...C arrays of pointer-sized elements to be filled with keys and values from the dictionary"
void CFDictionaryGetKeysAndValues(CFDictionaryRef theDict, const void **keys, const void **values);
hence
        const void **keys[dictSize];   
        const void **values[dictSize];
 CFDictionaryGetKeysAndValues(dictRef,*keys, *values);


On Jul 2, 2022, at 12:17 AM, Keary Suska <cocoa-dev@...> wrote:

I for one, did notice. I was simply taking for granted that manipulating an object passed by indirection is itself a bad coding practice and should be avoided at all costs, whether or not one is the author of the called code. The method should just request the object “directly” so the intent is clear. The use of indirection is pointless unless it is an out parameter (or an array of C strings, but that’s something altogether different). If it is both, then my argument stands, as the intent in unclear and is a bad code smell to boot.

As an out parameter, it is only problematic if the caller passes a subclass for a parent class. The caller might then be expecting the object to behave differently and errors could ensue. The reverse would be error free, which was my point that you missed.

So back to the complaint at hand—since the compiler may not be able to know the intent of the method, flagging the type mismatch may be a better thing to do, but it is also just the pedantic thing to do, and although technically correct, is it worth it to the compiler developers to implement it? The use cases for requiring flagging are outside of convention and involve edge cases. Should a compiler flag every possible stupid thing a programmer could do? The list would be endless! No, they pick and choose their battles, and hope that language conventions would prevent implementors from doing things, like, manipulating a parameter passed by indirection.

Keary Suska
Esoteritech, Inc.
"Demystifying technology for your home or business"

On Jul 1, 2022, at 3:32 PM, Jens Alfke <jens@...> wrote:



On Jun 29, 2022, at 6:44 PM, Roland King <rols@...> wrote:

There's been some discussion about what C++ would do in this case. Very simple, if NSDictionary were a superclass of NSMutableDictionary, which in a C++ hierarchy it would usually be, then supplying NSMutableDictionary pointer or reference to a method which took an NSDictionary pointer or reference would succeed

Argh. Am I the only person who noticed the double asterisks in the parameter types?
It’s NSDictionary** and NSMutableDictionary**
not NSDictionary* and NSMutableDictionary*
That makes a very large difference.

—Jens










Keary Suska
 

Because void * type is so generic, you would not get a compilation error/warning for any pointer type, at least as of C11. I believe you can even omit the const, as that would result in an implicit cast for the callee, IIRC.

Keary Suska
Esoteritech, Inc.
"Demystifying technology for your home or business"

On Jul 2, 2022, at 7:06 PM, Peter Teeson via groups.io <pteeson@...> wrote:

I noticed and sent this example of Apple’s code using CFDictionary… note the double asterisks for the keys and values parms.
hence an array of dictSize pointers, *keys[dictSize], and type const void *. No copmilation complaint.

I am not sure if this is a relevant example but this function, to get all the keys and values from a dictionary for you,
requires parameters for the keys and values to be

“ ...C arrays of pointer-sized elements to be filled with keys and values from the dictionary"
void CFDictionaryGetKeysAndValues(CFDictionaryRef theDict, const void **keys, const void **values);
hence
const void **keys[dictSize];
const void **values[dictSize];
CFDictionaryGetKeysAndValues(dictRef,*keys, *values);


On Jul 2, 2022, at 12:17 AM, Keary Suska <cocoa-dev@...> wrote:

I for one, did notice. I was simply taking for granted that manipulating an object passed by indirection is itself a bad coding practice and should be avoided at all costs, whether or not one is the author of the called code. The method should just request the object “directly” so the intent is clear. The use of indirection is pointless unless it is an out parameter (or an array of C strings, but that’s something altogether different). If it is both, then my argument stands, as the intent in unclear and is a bad code smell to boot.

As an out parameter, it is only problematic if the caller passes a subclass for a parent class. The caller might then be expecting the object to behave differently and errors could ensue. The reverse would be error free, which was my point that you missed.

So back to the complaint at hand—since the compiler may not be able to know the intent of the method, flagging the type mismatch may be a better thing to do, but it is also just the pedantic thing to do, and although technically correct, is it worth it to the compiler developers to implement it? The use cases for requiring flagging are outside of convention and involve edge cases. Should a compiler flag every possible stupid thing a programmer could do? The list would be endless! No, they pick and choose their battles, and hope that language conventions would prevent implementors from doing things, like, manipulating a parameter passed by indirection.

Keary Suska
Esoteritech, Inc.
"Demystifying technology for your home or business"

On Jul 1, 2022, at 3:32 PM, Jens Alfke <jens@...> wrote:



On Jun 29, 2022, at 6:44 PM, Roland King <rols@...> wrote:

There's been some discussion about what C++ would do in this case. Very simple, if NSDictionary were a superclass of NSMutableDictionary, which in a C++ hierarchy it would usually be, then supplying NSMutableDictionary pointer or reference to a method which took an NSDictionary pointer or reference would succeed
Argh. Am I the only person who noticed the double asterisks in the parameter types?
It’s NSDictionary** and NSMutableDictionary**
not NSDictionary* and NSMutableDictionary*
That makes a very large difference.

—Jens






Peter Teeson
 

If I remove the const, i.e. void**keys[dictSize], which was my original code, the compiler complains with "No matching function for call to CFDictionaryGetKeysAndValues

When I originally read up about const void* vs void* is why I tried omitting the const.
The documents actually discuss the fact the receiver does the cast.. but the compiler says “No matching…”
Took me a while to get it right and I still don’t really understand what is theoretically correct.

It seems to me that an array of const void* pointers should not be changed. But in fact the opaque code that fills in the keys with the value that point to the keys, and ditto for the value pointers, seems to not obey that.

The docs also point out that the mutability capability was added to the immutable code as opposed to being a separate TU.

Anyway from a pragmatic POV as long as it doesn’t crash at late binding time we move on.

respect…

Peter

On Jul 2, 2022, at 11:19 PM, Keary Suska <cocoa-dev@...> wrote:

Because void * type is so generic, you would not get a compilation error/warning for any pointer type, at least as of C11. I believe you can even omit the const, as that would result in an implicit cast for the callee, IIRC. 

Keary Suska
Esoteritech, Inc.
"Demystifying technology for your home or business"

On Jul 2, 2022, at 7:06 PM, Peter Teeson via groups.io <pteeson@...> wrote:

I noticed and sent this example of Apple’s code using CFDictionary… note the double asterisks for the keys and values parms. 
hence an array of dictSize pointers, *keys[dictSize], and type const void *. No copmilation complaint.

I am not sure if this is a relevant example but this function, to get all the keys and values from a dictionary for you,
requires parameters for the keys and values to be

“ ...C arrays of pointer-sized elements to be filled with keys and values from the dictionary"
void CFDictionaryGetKeysAndValues(CFDictionaryRef theDict, const void **keys, const void **values);
hence
       const void **keys[dictSize];   
       const void **values[dictSize];
CFDictionaryGetKeysAndValues(dictRef,*keys, *values);


On Jul 2, 2022, at 12:17 AM, Keary Suska <cocoa-dev@...> wrote:

I for one, did notice. I was simply taking for granted that manipulating an object passed by indirection is itself a bad coding practice and should be avoided at all costs, whether or not one is the author of the called code. The method should just request the object “directly” so the intent is clear. The use of indirection is pointless unless it is an out parameter (or an array of C strings, but that’s something altogether different). If it is both, then my argument stands, as the intent in unclear and is a bad code smell to boot.

As an out parameter, it is only problematic if the caller passes a subclass for a parent class. The caller might then be expecting the object to behave differently and errors could ensue. The reverse would be error free, which was my point that you missed.

So back to the complaint at hand—since the compiler may not be able to know the intent of the method, flagging the type mismatch may be a better thing to do, but it is also just the pedantic thing to do, and although technically correct, is it worth it to the compiler developers to implement it? The use cases for requiring flagging are outside of convention and involve edge cases. Should a compiler flag every possible stupid thing a programmer could do? The list would be endless! No, they pick and choose their battles, and hope that language conventions would prevent implementors from doing things, like, manipulating a parameter passed by indirection.

Keary Suska
Esoteritech, Inc.
"Demystifying technology for your home or business"

On Jul 1, 2022, at 3:32 PM, Jens Alfke <jens@...> wrote:



On Jun 29, 2022, at 6:44 PM, Roland King <rols@...> wrote:

There's been some discussion about what C++ would do in this case. Very simple, if NSDictionary were a superclass of NSMutableDictionary, which in a C++ hierarchy it would usually be, then supplying NSMutableDictionary pointer or reference to a method which took an NSDictionary pointer or reference would succeed

Argh. Am I the only person who noticed the double asterisks in the parameter types?
It’s NSDictionary** and NSMutableDictionary**
not NSDictionary* and NSMutableDictionary*
That makes a very large difference.

—Jens















Keary Suska
 

More modern versions of C compilers may be stricter about const, and I believe C++ is strict about it. I haven’t really kept up. Back in my C days I believe it was considered good practice to use const in argument parameters when you don’t plan to modify an argument, to keep you honest. In any cxase, it does look like it is a similar issue with a pedantic interpretation of type mismatch.

Keary Suska
Esoteritech, Inc.
"Demystifying technology for your home or business"

On Jul 3, 2022, at 11:01 AM, Peter Teeson via groups.io <pteeson@...> wrote:

If I remove the const, i.e. void**keys[dictSize], which was my original code, the compiler complains with "No matching function for call to ‘CFDictionaryGetKeysAndValues”

When I originally read up about const void* vs void* is why I tried omitting the const.
The documents actually discuss the fact the receiver does the cast.. but the compiler says “No matching…”
Took me a while to get it right and I still don’t really understand what is theoretically correct.

It seems to me that an array of const void* pointers should not be changed. But in fact the opaque code that fills in the keys with the value that point to the keys, and ditto for the value pointers, seems to not obey that.

The docs also point out that the mutability capability was added to the immutable code as opposed to being a separate TU.

Anyway from a pragmatic POV as long as it doesn’t crash at late binding time we move on.

respect…

Peter

On Jul 2, 2022, at 11:19 PM, Keary Suska <cocoa-dev@...> wrote:

Because void * type is so generic, you would not get a compilation error/warning for any pointer type, at least as of C11. I believe you can even omit the const, as that would result in an implicit cast for the callee, IIRC.

Keary Suska
Esoteritech, Inc.
"Demystifying technology for your home or business"

On Jul 2, 2022, at 7:06 PM, Peter Teeson via groups.io <pteeson@...> wrote:

I noticed and sent this example of Apple’s code using CFDictionary… note the double asterisks for the keys and values parms.
hence an array of dictSize pointers, *keys[dictSize], and type const void *. No copmilation complaint.

I am not sure if this is a relevant example but this function, to get all the keys and values from a dictionary for you,
requires parameters for the keys and values to be

“ ...C arrays of pointer-sized elements to be filled with keys and values from the dictionary"
void CFDictionaryGetKeysAndValues(CFDictionaryRef theDict, const void **keys, const void **values);
hence
const void **keys[dictSize];
const void **values[dictSize];
CFDictionaryGetKeysAndValues(dictRef,*keys, *values);


On Jul 2, 2022, at 12:17 AM, Keary Suska <cocoa-dev@...> wrote:

I for one, did notice. I was simply taking for granted that manipulating an object passed by indirection is itself a bad coding practice and should be avoided at all costs, whether or not one is the author of the called code. The method should just request the object “directly” so the intent is clear. The use of indirection is pointless unless it is an out parameter (or an array of C strings, but that’s something altogether different). If it is both, then my argument stands, as the intent in unclear and is a bad code smell to boot.

As an out parameter, it is only problematic if the caller passes a subclass for a parent class. The caller might then be expecting the object to behave differently and errors could ensue. The reverse would be error free, which was my point that you missed.

So back to the complaint at hand—since the compiler may not be able to know the intent of the method, flagging the type mismatch may be a better thing to do, but it is also just the pedantic thing to do, and although technically correct, is it worth it to the compiler developers to implement it? The use cases for requiring flagging are outside of convention and involve edge cases. Should a compiler flag every possible stupid thing a programmer could do? The list would be endless! No, they pick and choose their battles, and hope that language conventions would prevent implementors from doing things, like, manipulating a parameter passed by indirection.

Keary Suska
Esoteritech, Inc.
"Demystifying technology for your home or business"

On Jul 1, 2022, at 3:32 PM, Jens Alfke <jens@...> wrote:



On Jun 29, 2022, at 6:44 PM, Roland King <rols@...> wrote:

There's been some discussion about what C++ would do in this case. Very simple, if NSDictionary were a superclass of NSMutableDictionary, which in a C++ hierarchy it would usually be, then supplying NSMutableDictionary pointer or reference to a method which took an NSDictionary pointer or reference would succeed
Argh. Am I the only person who noticed the double asterisks in the parameter types?
It’s NSDictionary** and NSMutableDictionary**
not NSDictionary* and NSMutableDictionary*
That makes a very large difference.

—Jens







Keary Suska
 

Oop, I am also forgetting that these are double pointers, so it matters much more, especially since it is an out parameter. One might end up trying to modify a const later in the code. That is less pedantic and more of a sanity check. If it was just a const void *, and not a const void **, you might not get a compiler warning.

Keary Suska
Esoteritech, Inc.
"Demystifying technology for your home or business"

On Jul 3, 2022, at 11:45 AM, Keary Suska <cocoa-dev@...> wrote:

More modern versions of C compilers may be stricter about const, and I believe C++ is strict about it. I haven’t really kept up. Back in my C days I believe it was considered good practice to use const in argument parameters when you don’t plan to modify an argument, to keep you honest. In any cxase, it does look like it is a similar issue with a pedantic interpretation of type mismatch.

Keary Suska
Esoteritech, Inc.
"Demystifying technology for your home or business"

On Jul 3, 2022, at 11:01 AM, Peter Teeson via groups.io <pteeson@...> wrote:

If I remove the const, i.e. void**keys[dictSize], which was my original code, the compiler complains with "No matching function for call to ‘CFDictionaryGetKeysAndValues”

When I originally read up about const void* vs void* is why I tried omitting the const.
The documents actually discuss the fact the receiver does the cast.. but the compiler says “No matching…”
Took me a while to get it right and I still don’t really understand what is theoretically correct.

It seems to me that an array of const void* pointers should not be changed. But in fact the opaque code that fills in the keys with the value that point to the keys, and ditto for the value pointers, seems to not obey that.

The docs also point out that the mutability capability was added to the immutable code as opposed to being a separate TU.

Anyway from a pragmatic POV as long as it doesn’t crash at late binding time we move on.

respect…

Peter

On Jul 2, 2022, at 11:19 PM, Keary Suska <cocoa-dev@...> wrote:

Because void * type is so generic, you would not get a compilation error/warning for any pointer type, at least as of C11. I believe you can even omit the const, as that would result in an implicit cast for the callee, IIRC.

Keary Suska
Esoteritech, Inc.
"Demystifying technology for your home or business"

On Jul 2, 2022, at 7:06 PM, Peter Teeson via groups.io <pteeson@...> wrote:

I noticed and sent this example of Apple’s code using CFDictionary… note the double asterisks for the keys and values parms.
hence an array of dictSize pointers, *keys[dictSize], and type const void *. No copmilation complaint.

I am not sure if this is a relevant example but this function, to get all the keys and values from a dictionary for you,
requires parameters for the keys and values to be

“ ...C arrays of pointer-sized elements to be filled with keys and values from the dictionary"
void CFDictionaryGetKeysAndValues(CFDictionaryRef theDict, const void **keys, const void **values);
hence
const void **keys[dictSize];
const void **values[dictSize];
CFDictionaryGetKeysAndValues(dictRef,*keys, *values);


On Jul 2, 2022, at 12:17 AM, Keary Suska <cocoa-dev@...> wrote:

I for one, did notice. I was simply taking for granted that manipulating an object passed by indirection is itself a bad coding practice and should be avoided at all costs, whether or not one is the author of the called code. The method should just request the object “directly” so the intent is clear. The use of indirection is pointless unless it is an out parameter (or an array of C strings, but that’s something altogether different). If it is both, then my argument stands, as the intent in unclear and is a bad code smell to boot.

As an out parameter, it is only problematic if the caller passes a subclass for a parent class. The caller might then be expecting the object to behave differently and errors could ensue. The reverse would be error free, which was my point that you missed.

So back to the complaint at hand—since the compiler may not be able to know the intent of the method, flagging the type mismatch may be a better thing to do, but it is also just the pedantic thing to do, and although technically correct, is it worth it to the compiler developers to implement it? The use cases for requiring flagging are outside of convention and involve edge cases. Should a compiler flag every possible stupid thing a programmer could do? The list would be endless! No, they pick and choose their battles, and hope that language conventions would prevent implementors from doing things, like, manipulating a parameter passed by indirection.

Keary Suska
Esoteritech, Inc.
"Demystifying technology for your home or business"

On Jul 1, 2022, at 3:32 PM, Jens Alfke <jens@...> wrote:



On Jun 29, 2022, at 6:44 PM, Roland King <rols@...> wrote:

There's been some discussion about what C++ would do in this case. Very simple, if NSDictionary were a superclass of NSMutableDictionary, which in a C++ hierarchy it would usually be, then supplying NSMutableDictionary pointer or reference to a method which took an NSDictionary pointer or reference would succeed
Argh. Am I the only person who noticed the double asterisks in the parameter types?
It’s NSDictionary** and NSMutableDictionary**
not NSDictionary* and NSMutableDictionary*
That makes a very large difference.

—Jens











 



On Jul 3, 2022, at 10:01 AM, Peter Teeson via groups.io <pteeson@...> wrote:

It seems to me that an array of const void* pointers should not be changed. But in fact the opaque code that fills in the keys with the value that point to the keys, and ditto for the value pointers, seems to not obey that.

"const void**”, as it’s being used in this function, means “an array of const pointers to anything”. So the array is changeable; what’s read-only is the memory the pointers in the array point to. That makes sense for this API since the CFDictionary never alters the keys or values itself.

In C++ at least, you probably have to add a cast to pass an array of non-void pointers to such a function. That’s because C++ won’t implicitly convert a T** to a void** for any non-void type T. And that’s for exactly the same reason that we’ve been discussing. Though T* is compatible with void* the reverse isn’t the case (in C++), and the double-indirection means both directions can happen and have to be possible.

—Jens