Other than this one function, I want everything else to work the same. But if I write another generic function, it will always call the first version and not the specialized one:
func qwe<T: A>(_: T.Type) {
asd(T.self)
}
Is there any way I can use the specialized asd in this case without having to copy paste qwe with <T: B> generic parameter?
protocol A {}
protocol B: A {}
func asd<T: A>(_: T.Type) { print("asd<T: A>() called") }
func asd<T: B>(_: T.Type) { print("asd<T: B>() called") }
func qwe<T: A>(_: T.Type) { asd(T.self) }
struct SA : A {}
struct SB : B {}
func test() {
qwe(SA.self) // asd<T: A>() called
qwe(SB.self) // asd<T: A>() called
}
test()
This happens because the constraint on qwe's generic type parameter T is that it conforms to A (not B). That is, all the compiler can know about T is that it is some type that conforms to A, and it cannot just make a random guess that it might conform to B or C or whatever.
I'm not sure if the following would work in your actual use case but it's what I did in what I think might be a similar scenario:
protocol A {
static func asd()
}
protocol B: A {}
extension A {
static func asd() { print("A.asd() called") }
}
extension B {
static func asd() { print("B.asd() called") }
}
func qwe<T: A>(_: T.Type) { T.asd() }
struct SA : A {}
struct SB : B {}
func test() {
qwe(SA.self) // A.asd() called
qwe(SB.self) // B.asd() called
}
test()
Or simpler, if what you want is specific functions for the concrete types SA and SB:
protocol A {
static func asd()
}
struct SA : A {
static func asd() { print("SA.asd() called") }
}
struct SB : A {
static func asd() { print("SB.asd() called") }
}
func qwe<T: A>(_: T.Type) { T.asd() }
func test() {
qwe(SA.self) // SA.asd() called
qwe(SB.self) // SB.asd() called
}
test()
This happens because the constraint on qwe 's generic type parameter T is that it conforms to A (not B ). That is, all the compiler can know about T is that it is some type that conforms to A , and it cannot just make a random guess that it might conform to B or C or whatever.
From my understanding, that would only be true if asd wasn't generic and instead had an A.Type parameter. (My actual code uses associated types for these protocols, so I couldn't even use non-generic functions.) And if you are using generics, the compiler knows exactly what type you are using.
For example changing the current example code like this:
func qwe(_ type: A.Type) { asd(type.self) }
You would get a compiler error because you tried to call a generic function with a non-concrete type.
No, this is not a bug in the compiler. It is expected behavior, and you'll have to adjust your mental model to understand it.
: )
I'm afraid I cannot explain it better than in my above attempt but I'm sure others can.
This will not compile because you mention T without declaring it as a generic type parameter. (I don't see how that has anything to do with the question of the OP.)
If you write a function like this:
func zxc<T>(_: T.Type) { /* … */ }
Then the type paramter T is totally unconstrained (meaning nothing is known about it at compile time) and therefore it is essentially the same as:
This would compile if you had declared an overload of asd like the following (which takes the type of the protocol A, not a type conforming to the protocol A):
But again, I fail to see what it has to do with your original question.
I think what you need to understand is exemplified by this program:
func zxc<T>(_: T.Type) {
// There is *runtime* info about `T` here, so we can print its type:
print("Printing runtime type of `T` from zxc:\n ", T.self)
// But at compile time, nothing is known about `T` here, because
// `T` is totally unconstrained. So this:
print("Calling qaz(T.self) from zxc:")
qaz(T.self)
// Will only be able to call a qaz for a totally unconstrained type.
// So it will always, no matter the runtime type of T, be calling
// qaz<T>(), and never eg qaz<T: Numeric>().
}
func qaz<T>(_: T.Type) { print(" qaz<T>()") }
func qaz<T: Numeric>(_: T.Type) { print(" qaz<T: Numeric>()") }
func test() {
zxc(Int.self)
print("Calling qaz(Int.self) from test:")
qaz(Int.self)
}
test()
Which will print:
Printing runtime type of `T` from zxc:
Int
Calling qaz(T.self) from zxc:
qaz<T>()
Calling qaz(Int.self) from test:
qaz<T: Numeric>()
Remember that Swift's generics is not like eg C++ templates.
Again, the only thing the compiler can know about a generic type parameter is what you have explicitly told it about that generic type parameter, via constraints.
It doesn't matter what the type of the arguments are at the call sites. It is not like C++ templates where each different call site result in a different version of the function.
func qwe<T >(_: T.Type) { /* compiler knows nothing about T */ }
func qwe<T: A>(_: T.Type) { /* compiler knows that T conforms to A */ }
func qwe<T: B>(_: T.Type) { /* compiler knows that T conforms to B */ }
Swift's generics are a compile time feature, and what it knows at compile time about a certain function's T is only decided by the explicit constraints on that T. Code that calls those functions does not matter at all for what's knowable at compile time within the function definitions.
struct TypeErased {
let block: () -> ()
}
protocol A {
static func eraseType() -> TypeErased
}
protocol B: A {}
extension A {
static func eraseType() -> TypeErased {
TypeErased {
asd(Self.self)
}
}
}
extension B {
static func eraseType() -> TypeErased {
TypeErased {
asd(Self.self)
}
}
}
func asd<T: A>(_: T.Type) { print("asd<T: A>() called") }
func asd<T: B>(_: T.Type) { print("asd<T: B>() called") }
func qwe<T: A>(_ type: T.Type) {
if T.self is B.Type {
print("T is B but calling asd with it results in compiler error")
T.eraseType().block()
} else {
asd(type.self)
}
}
class SB : B {}
qwe(SB.self)
This will correctly call asd<T: B>(_:) but the problem is that
if T.self is B.Type
Only works if B doesn't have associated types. And in my real use case, it has, so basically I can keep this public API but I always have to use type erasure to then call back to my real function instead of just when the wrong function is called.
Hopefully that plus one layer of calls won't affect performance.
In Swift, the static type (at compile time) is not the same as the dynamic type (at run time). Swift generics are a static feature, and type casting using is, as? or as! is a dynamic operation. I find a good rule of thumb is that mixing generics and dynamic casts is to be avoided.
Without knowing the specifics, it’s hard to say, but based on your initial question it looks like you want a dynamically dispatched asd. For that it needs to be a protocol requirement spelled as a method.
Assuming the free generic asd functions must be the way they are, and should be called via another free qwe function that is the way it is, and assuming that you can add requirements and default implementations on A and B, then you can wrap the asd functions within default implementations of a requirement like this:
protocol A {
static func callAsd()
}
protocol B: A {}
func asd<T: A>(_: T.Type) { print("asd<T: A>() called") }
func asd<T: B>(_: T.Type) { print("asd<T: B>() called") }
extension A {
static func callAsd() {
asd(self) // <-- Compiler chooses asd<T: A> overload here.
}
}
extension B {
static func callAsd() {
asd(self) // <-- Compiler chooses asd<T: B> overload here.
}
}
func qwe<T: A>(_: T.Type) { T.callAsd() }
struct SA : A {}
struct SB : B {}
func test() {
qwe(SA.self) // asd<T: A>() called
qwe(SB.self) // asd<T: B>() called
}
test()