7

Here is an example playground:

protocol P {
    associatedtype T
    func getValue() -> T
}

class Foo: P {
    func getValue() -> String {
        return "hello"
    }
}

class Bar {
    func test<T: P>(_ o: T) {
        print("Generic", o.getValue())
    }

    func test(_ o: Any) {
        print("Any")
    }
}

let foo = Foo()
let bar = Bar()
bar.test(foo)

This outputs: Any.

If I remove the Any version of test, the generic method is called. Class Foo conforms to protocol P, why does Swift not pick the generic method since it is more specific? Is there a way to call the generic one?

Dan Beaulieu
  • 19,406
  • 19
  • 101
  • 135
Arnol
  • 2,107
  • 2
  • 17
  • 19
  • Well the simplest solution would be to make the `Any` overload of `test(_:)` generic – i.e `func test(_ o: T)` (not sure if this directly answers your question though). – Hamish Jan 08 '17 at 10:58
  • The generic method is not more specific in this case. The argument to `test(_ o: Any)` is one of a _concrete type_ of `Any` (even if the type itself is a protocol), which can successfully wrap a `Foo` instance as its argument. Any concrete type will be more specific than a generic one, and the former will take precedence in the overload resolution when calling `bar.test(...)`. You should generally avoid using `Any` as a concrete type, and a better approach would be subst. with the non-constrained generic described by @Hamish above (in which case the constrained generic will be more specific). – dfrib Jan 08 '17 at 11:03

1 Answers1

3

From what I understand, the compiler will always favour an explicitly typed parameter over a generic one when performing overload resolution. Thus in the resolution between test<T : P>(_ o: T) and test(_ o: Any) – the latter will be preferred as it has an explicit (albeit abstract) parameter type, whereas the first is merely a placeholder.

Therefore if you make the second overload generic as well, the compiler will now favour the first overload, as they both don't have explicitly typed parameters, but the first overload is more tightly constrained:

class Bar {
    func test<T: P>(_ o: T) {
        print("Generic", o.getValue())
    }

    func test<T>(_ o: T) {
        print("Any")
    }
}

let foo = Foo()
let bar = Bar()
bar.test(foo) // Generic hello

Keeping the overloads as-is, type-casting in order to disambiguate also appears to be a viable solution:

class Bar {
    func test<T: P>(_ o: T) {
        print("Generic", o.getValue())
    }

    func test(_ o: Any) {
        print("Any")
    }
}

let foo = Foo()
let bar = Bar()
(bar.test as (Foo) -> Void)(foo) // Generic hello

Although I would strongly recommend the first approach, as it allows you to reason better about what overload will be chosen (generics should also be preferred in general over protocol-typed parameters wherever possible, due to the performance benefits of specialisation).

Community
  • 1
  • 1
Hamish
  • 78,605
  • 19
  • 187
  • 280
  • I believe that protocols as types are generally not referred to as abstract, but rather _nonspecific_. Possible the former is avoided since we may actually have instances of these types (not just using them as generic typeholders). – dfrib Jan 08 '17 at 11:13
  • @dfri Huh, I've always referred to them as abstract (being the antonym of concrete) – although I would like to make a slight differentiation between "having instances of these types" and "having an instance typed as this". You cannot have instances of protocol types, you can only have instances of concrete types, typed as protocol types. It's certainly an interesting point though, would be interested in what the community consensus is on the use of the word "abstract" here. – Hamish Jan 08 '17 at 11:24
  • True with the differentiation. I believe the interesting discussion is how (or whether) we want to differentiate the description of abstract typeholders (such as a generic typeholder, `T`, above, or typealiases) vs instances typed as protocols (the latter without using generics or typealiases/associated types). I reacted to abstract as I've never really seen the term used (Swift, specifically) for other than the former in swift docs, whereas for the latter, at least `Any` (typed) is described simply as a nonspecific type in the language guide. But maybe this is potatos and potatoes :) – dfrib Jan 08 '17 at 11:35
  • Based on Arnolds talk in WWDC2016-416, protocols used as types will resolve to an existential container (making use, dynamically, of heap storage) that can hold different concrete instances of types conforming to the protocol, and the actual type of these "existentially wrapped" instances will not be known at compile time. Whereas for abstract typeholders, (I believe) these will resolve to concrete types at compile time, so there is (if I'm not mistaken) a differentiation here between the two cases. But I would need feedback on this train of thought (notice the uncertain. paranthesis above) :) – dfrib Jan 08 '17 at 12:05
  • @dfri My feeling is that it's fine to refer to both generic placeholder and protocol-typed parameters as abstract due to the fact that neither refers to a specific concrete type. Although as you say, once the overload is resolved, a generic placeholder will be resolved to a concrete type on the caller's side (and on the callee's side as an optimisation). However, given that we're talking about the process of overload resolution itself, I'd say it's fine to refer to both as abstract :) – Hamish Jan 08 '17 at 12:51
  • Note that the specific internal details of how the compiler deals with protocol-typed instances (existential containers etc.) aren't too relevant given that it's also possible for such details to be optimised out through, for example, inlining (in such cases, the concrete type would be known by the compiler). – Hamish Jan 08 '17 at 12:51
  • Thanks for the discussion – dfrib Jan 08 '17 at 15:01