Wrapped constrained generic functions

If my understanding is correct, Swift prioritises overloaded functions (and initialisers) in the order of most constrained to least constrained.

In the following example:

struct Widget<T> {
  
  init() where T: FloatingPoint {
    print("constrained")
  }
  
  init() {
    print("non-constrained")
  }
}

struct Container<T> {
  
  let contained: Widget<T>
  
  init() {
    contained = Widget()
  }
}

let boolWidget = Widget<Bool>()
let doubleWidget = Widget<Double>()
let boolConatiner = Container<Bool>()
let doubleContainer = Container<Double>()

I would expect both the Double constrained instances to emit constrained on instantiation. But instead, only the instantiation of the non-wrapped Widget<Double> does – Container<Double> emits non-constrained, which isn't what I'd expect.

Is there something I'm not getting, or is this a known limitation or a bug?

I think that would only work if Container also had its own more constrained initialiser, init() where T: FloatingPoint. Within the scope of the unconstrained init() the type system doesn't see but an unconstrained type T.

1 Like

Yeah, it's a shame it needs to be cascaded manually. Seems like the type system has enough there to do it. Thanks for your response though, appreciate it.

Would be really curious to know if the type system could do this, or there’s a particular reason it doesn’t.

Swift's generics system isn't a template system -- the body of init is typechecked one time, using the information that is available at the time it is typechecked, not every time it's called with a concrete type. When the compiler typechecks Widget<T>.init(), the only context it has is that T is a type.

6 Likes

Thanks for satisfying my curiosity here, I appreciate it.

So one more question: when the compiler looks at a generic function to work out if it can be specialized, does it perform this recursively? Or is it just at the top-level where the concrete type is specified?

If it’s recursive, why does the compiler have context in this situation and not the one described in the original post?

Specialization happens during optimization, after typechecking, once all function calls have been resolved. It's totally possible (and very, very likely) that Widget<Double>.init() would be specialized and inlined, but that specialization won't affect overload resolution, since that's all been wrapped up by the time specialization happens.

3 Likes

Got it, OK. I think I’ve got templating on my wish list if that’s what it would take :blush:. Thanks for you thoughts.