How to determine which initializer is called?


(Alfred Zien) #1

I have some code that abstract over String and Substring, wrapped around in function with signature

func foo<T: StringProtocol>(param: T) {...}

Inside, in some cases, I need concrete string, which I'm getting with String(element). But when I need to pass initializer as function to map or flatMap, I get error

nullableElement.map(String.init)  // Ambiguous use of 'init'

Of course this happens because there are initializers that takes any object, for example String.init(describing:) or String.init(reflecting:). Is there any possibility to say "I want initializer without named arguments", something like String.init(:) ?

And I have more general question – how to determine which function overload is chosen by compiler in cases like String(element)? Because I'm still struggling to find which of them is used.


#2

That’d be String.init(_:).


#3

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.


(Jordan Rose) #4

If you're in an IDE, you can generally ask for Quick Help (option-click in Xcode) on the thing being called to find out which overload is being used. That doesn't directly work with a type, though (because it shows you the type), so you might have to add the ".init" explicitly, then check, then remove it.


#5

If necessary, you can also disambiguate with as:

let myInit = Foo.init as (Int)->Foo

(Alfred Zien) #6

Thanks to everyone! I've found the specific overload:

public init<S>(_ other: S) 
  where S : LosslessStringConvertible, S : Sequence, S.Element == Character

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 .

May be it depends on whether or not compiler will choose to specialize generic type?


#7

I doubt that is the case, specialization is hidden from programmer. You have no way of enforcing or opting-out of it. If it is indeed the case it’ll likely be a bug.
I also print T.self to make sure that compiler knows full well at compile time what T is.