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.