Incorrect general overload function called

I am trying to understand why the where clause of a generic method is ignored

I made a simple usage example in Swift 3 (you can copy the code on the playground if you want to play with it):

//MARK: - Classes protocol HasChildren { var children:[Human] {get} } class Human {} class SeniorHuman : Human, HasChildren { var children: [Human] { return [AdultHuman(), AdultHuman()] } } class AdultHuman : Human, HasChildren { var children: [Human] { return [YoungHuman(), YoungHuman(), YoungHuman()] } } class YoungHuman : Human {} //MARK: - Generic Methods /// This method should only be called for YoungHuman func sayHelloToFamily<T: Human>(of human:T) { print("Hello \(human). You have no children. But do you conform to protocol? \(human is HasChildren)") } /// This method should be called for SeniorHuman and AdultHuman, but not for YoungHuman... func sayHelloToFamily<T: Human>(of human:T) where T: HasChildren { print("Hello \(human). You have \(human.children.count) children, good for you!") } 

So now let's run some tests. If we have:

 let senior = SeniorHuman() let adult = AdultHuman() print("Test #1") sayHelloToFamily(of: senior) print("Test #2") sayHelloToFamily(of: adult) if let seniorFirstChildren = senior.children.first { print("Test #3") sayHelloToFamily(of: seniorFirstChildren) print("Test #4") sayHelloToFamily(of: seniorFirstChildren as! AdultHuman) } 

Output:

 Test #1 Hello SeniorHuman. You have 2 children, good for you! Test #2 Hello AdultHuman. You have 3 children, good for you! Test #3 Hello AdultHuman. You have no children. But do you conform to protocol? true //Well, why are you not calling the other method then? Test #4 Hello AdultHuman. You have 3 children, good for you! //Oh... it working here... It seems that I just can't use supertyping 

Well ... apparently, in order to work on the where protocol proposal, we need to pass a strong type that matches the protocol in its definition.

Just using supertypes is not enough, even if in test No. 3 it is obvious that this instance really matches the HasChildren protocol.

So, what am I missing here, is it simply impossible? Do you have links containing additional information about what is happening, or more information about where clauses or subtypes and their behavior in general?

I read some useful resources, but none of them have an exhaustive explanation of why it does not work:

+5
source share
2 answers

The type of method to be called is selected at compile time. What does the compiler know about your types?

 if let seniorFirstChildren = senior.children.first { 

seniorFirstChildren is Human because it is declared as children . We have no information on whether the child an adult or an older person.

However, consider the following:

 if let seniorFirstChildren = senior.children.first as? AdultHuman { 

Now the compiler knows seniorFirstChildren is AdultHuman , and it will call the method that you expect.

You must distinguish between static types (types known at compile time) and dynamic types (types known at run time).

+3
source

From Language Guide - Casting by Type :

Check type

Use the type check operator ( is ) to check if an instance has a particular subclass type. The type checking operator returns true if an instance of this type is a subclass and false if it is not.

The check statement of type is allowed at run time, while allowing overloading the sayHelloToFamily call using the first member of your AdultHuman instance (bound to seniorFirstChildren ) as an argument is allowed at compile time (in this case, it is entered as Human , which does not match HasChildren ). If you explicitly tell the compiler that seniorFirstChildren is an instance of AdultHuman (using unsafe as! AdultHuman ), then, of course, the compiler will use this information to select a more specific overload.

+3
source

Source: https://habr.com/ru/post/1262734/


All Articles