This is quite an interesting question, and quite frankly, I didn’t know until I did the experiment. Generally, Swift will choose overload that is most specific to the argument types. Since we don’t have control over String.init(_:), let me use a dummy example:
protocol Base { }
protocol Protocol: Base { }
struct ConcreteBase: Base { }
struct ConcreteProtocol: Protocol { }
struct S {
init(_: Base) { print("Base") }
init(_: Protocol) { print("Protocol") }
}
func foo<T: Base>(param: T) {
print(T.self)
S(param)
}
foo(param: ConcreteBase())
// ConcreteBase
// Base
foo(param: ConcreteProtocol())
// ConcreteProtocol
// Base
So S.init(_:) in foo will have 2 overloads:
S.init(_: Base)
S.init(_: Protocol)
So if argument is known to be Base, but not protocol, it’ll use first overload.
If it is known to be Protocol, it will also be Base so both overloads are viable. Swift will choose the second one as it is more specific.
Now, since T is known to be subclass of Base but not necessarily Protocol, foo will use S.init(_: Base).
The tricky part is that T is a placeholder type, and so will depend at call site. That is, compiler may have more information. The question is, does compiler utilize this information to decide overload?.
The first invocation is trivial. T is a ConcreteBase so it still use base implementation.
In the second invocation, T is a ConcreteProtocol which is also Protocol, so if compiler utilize this information, it’ll use protocol implementation, I believe C++ have this behaviour, but I’m not so sure. In any case, Swift doesn’t seem to utilize the fact that T is now a ConcreteProtocol to decode overload as it still uses base implementation.
Note though that this applies to static dispatch. I think protocol‘s requirement uses dynamic dispatch and behave differently.