Having a Generic function call another Generic function

protocol A {}
struct Struct: A {}

func bar<T>(_ param: T) -> String {
	return "Unconstrained"
}

func bar<T>(_ param: T) -> String where T: A {
	return "A"
}

func foo<Type>(_ param: Type) -> String {
	return bar(param)
}
print(foo(Struct()))

Why does this print Unconstrained ?. Does the the actual type of Type parameter in function foo get lost? If I constrain foo to foo<Type: A> it does print A, but that's not what I need unfortunately.

I imagine that swift only compiles one version of function foo, so that real specializations like in C++ are not really possible unfortunately?

Is there any way to make this work in Swift?

If foo() is not constrained to A, then how is the compiler meant to know that Struct implements A?

As with virtual methods, the search for a complying function is likely to start at the lowest known level, which means it is going to be happy with the less specialised function, not bothering to search any further once it has found it.

Comment out the unconstrained function and the code will not compile because foo() knows nothing about the implementation in Struct of A.

Constrain foo() to A and everything works fine, whichever version of bar() you comment out.

Why do you feel you don't want to constrain foo() to A? This sounds like a design problem rather than the compiler.

I'm basically trying to specialize a function for one special type where I have a much more optimal algorithm for. For example:

protocol FastDoable {
   var magicFieldThatMakesDoingStuffFast: Int
}

func do<T>(_ items: [T]) -> [T]  {
  // use slow, but generic implementation
}

func do<T>(_ items: [T] -> [T] where T: FastDoable {
 // use fast implementation relying on FastDoable
}

func someFunctionThatDoesAllTheWork<T>(_ items: [T]) {
   let newItems = do(items)
   // do more work with newItems
}

Generic overloads are resolved at compile-time, not run-time. Within the body of ‘foo’, all the Compiler sees is ‘T’, so the unconstrained version of ‘bar’ is selected.

If you add a second, constrained version of ‘foo’ (which is then called with a concrete type), it will select the constrained version of both ‘foo’ and ‘bar’

Try this :

protocol A {}

struct NonA { }

struct Struct: A {}

func bar<T>(_ param: T) -> String
{
  return "Unconstrained"
}

func bar(_ param: A) -> String
{
  return "A"
}

func foo<T>(_ param: T) -> String
{
  if let aParam = param as? A
  {
    return bar(aParam)
  }
  
  return bar(param)
}

  {
    let nonA = NonA()
    
    print(foo(a))
    
    let s = Struct()
    
    print(foo(s))
  }

Don't forget a protocol is already a generic type of sorts.

That's the route I have taken indeed. Guess I have to get used to Swift generics and C++ templates being fundamentally different.

1 Like

You could refactor that so that the sort‐cutable logic is a requirement of the protocol. As a protocol requirement, it will be dispatched dynamically.

protocol Doable {
   func doSomethingAsFastAsPossible()
}

extension Doable {
    func doSomethingAsFastAsPossible() {
        // use slow, but generic implementation
        // Used by any type that doesn’t provide anything better.
    }
}

extension Doable where Self : FastDoable {
    func doSomethingAsFastAsPossible() {
         // use fast implementation relying on FastDoable
         // Types that are FastDoable will conform using this overload.
    }
}

func someFunctionThatDoesAllTheWork<T>(_ items: [T]) where T : Doable {
    for item in items {
        item.doSomethingAsFastAsPossible()
    }
    // And so on...
}
2 Likes