P.Type vs. P.Protocol
There are two kinds of protocol metatypes. For some protocol P, and a conforming type C:
- A P.Protocoldescribes the type of a protocol itself (the only value it can hold isP.self).
- A P.Typedescribes a concrete type that conforms to the protocol. It can hold a value ofC.self, but notP.selfbecause protocols don't conform to themselves (although one exception to this rule isAny, asAnyis the top type, so any metatype value can be typed asAny.Type; includingAny.self).
The problem you're facing is that for a given generic placeholder T, when T is some protocol P, T.Type is not P.Type – it is P.Protocol.
So if we jump back to your example:
protocol P {}
class C : P {}
func printType<T>(serviceType: T.Type) {
    print(serviceType)
}
let test: P.Type = C.self
// Cannot invoke 'printType' with an argument list of type '(serviceType: P.Type)'
printType(serviceType: test)
We cannot pass test as an argument to printType(serviceType:). Why? Because test is a P.Type; and there's no substitution for T that makes the serviceType: parameter take a P.Type.
If we substitute in P for T, the parameter takes a P.Protocol:
printType(serviceType: P.self) // fine, P.self is of type P.Protocol, not P.Type
If we substitute in a concrete type for T, such as C, the parameter takes a C.Type:
printType(serviceType: C.self) // C.self is of type C.Type
Hacking around with protocol extensions
Okay, so we've learnt that if we can substitute in a concrete type for T, we can pass a C.Type to the function. Can we substitute in the dynamic type that the P.Type wraps? Unfortunately, this requires a language feature called opening existentials, which currently isn't directly available to users.
However, Swift does implicitly open existentials when accessing members on a protocol-typed instance or metatype (i.e it digs out the runtime type and makes it accessible in the form of a generic placeholder). We can take advantage of this fact in a protocol extension:
protocol P {}
class C : P {}
func printType<T>(serviceType: T.Type) {
  print("T.self = \(T.self)")
  print("serviceType = \(serviceType)")
}
extension P {
  static func callPrintType/*<Self : P>*/(/*_ self: Self.Type*/) {
    printType(serviceType: self)
  }
}
let test: P.Type = C.self
test.callPrintType()
// T.self = C
// serviceType = C
There's quite a bit of stuff going on here, so let's unpack it a little bit:
- The extension member - callPrintType()on- Phas an implicit generic placeholder- Selfthat's constrained to- P. The implicit- selfparameter is typed using this placeholder.
 
- When calling - callPrintType()on a- P.Type, Swift implicitly digs out the dynamic type that the- P.Typewraps (this is the opening of the existential), and uses it to satisfy the- Selfplaceholder. It then passes this dynamic metatype to the implicit- selfparameter.
 
- So, - Selfwill be satisfied by- C, which can then be forwarded onto- printType's generic placeholder- T.
 
Why is T.Type not P.Type when T == P?
You'll notice how the above workaround works because we avoided substituting in P for the generic placeholder T. But why when substituting in a protocol type P for T, is T.Type not P.Type?
Well, consider:
func foo<T>(_: T.Type) {
    let t: T.Type = T.self
    print(t)
}
What if we substituted in P for T? If T.Type is P.Type, then what we've got is:
func foo(_: P.Type) {
    // Cannot convert value of type 'P.Protocol' to specified type 'P.Type'
    let p: P.Type = P.self
    print(p)
}
which is illegal; we cannot assign P.self to P.Type, as it's of type P.Protocol, not P.Type.
So, the upshot is that if you want a function parameter that takes a metatype describing any concrete type that conforms to P (rather than just one specific concrete conforming type) – you just want a P.Type parameter, not generics. Generics don't model heterogenous types, that's what protocol types are for.
And that's exactly what you have with printType(conformingClassType:):
func printType(conformingClassType: P.Type) {
    print(conformingClassType)
}
printType(conformingClassType: test) // okay
You can pass test to it because it has a parameter of type P.Type. But you'll note this now means we cannot pass P.self to it, as it is not of type P.Type:
// Cannot convert value of type 'P.Protocol' to expected argument type 'P.Type'
printType(conformingClassType: P.self)