3

I am using Swift 2.2 on XCode 7.3.1 and trying to call a Generic function from another Generic function.

Code

class Thing1 {
     let variable: SomeProtocol
     init<A: SomeProtocol>(variable: A) {
          self.variable = variable
          self.add1(self.variable)
     }

     func add1<A: SomeProtocol>(stuff: A) {
          let thing: Thing2 = Thing2()
          thing.add2(stuff)
     }

}

class Thing2 {
    func add2<A: SomeProtocol>(stuff: A) {

    }
}

protocol SomeProtocol { }


add1("a") // Cannot invoke 'add1' with an argument list of type '(String)'
add1(4) // Cannot invoke 'add1' with an argument list of type '(Int)'

I get the error.

'Cannot invoke add with an argument of list type '(Whatever type I used to call the function)''
Cody Weaver
  • 4,756
  • 11
  • 33
  • 51
  • I cleaned up the code a little bit. In a playground it compiles fine but when you try and call 'add1' it doesn't let you. – Cody Weaver Aug 11 '16 at 14:55
  • @CodyWeaver Can you please post a minimal example that's as close to compiling as you can get? This way, we're all testing consistently, without having to come up with our own implementations of `SomeProtocol`, `Thing`, etc. – Alexander Aug 11 '16 at 15:11
  • Added the code to make it more clear what is going on. – Cody Weaver Aug 11 '16 at 15:20
  • 1
    @CodyWeaver Have you conformed the types that you wish to have access to `add1` to `SomeProtocol`? E.g. `extension String : SomeProtocol { }` and `extension Int : SomeProtocol { }`. – dfrib Aug 11 '16 at 15:22
  • Yeah I added the extensions. I also added way more code to show more of the use case. – Cody Weaver Aug 11 '16 at 15:30

2 Answers2

2

The problem is that abstract types in Swift don't necessarily conform to themselves – therefore you cannot use a SomeProtocol typed thing as a concrete typed thing that conforms to SomeProtocol (which is what your add1 generic function expects as an argument).

The simplest solution in your case therefore is just to use the generic variable argument, rather than the variable property, as because it's a generic, it's typed as a concrete thing that conforms to SomeProtocol, which can therefore be passed into your add1 function:

init<A: SomeProtocol>(variable: A) {
    self.variable = variable
    add1(variable)
}

However in order to prevent these kind of issues later down the line, you may want to consider making your class generic, assuming that your variable property should be of constant type throughout the lifetime of a given Thing1 instance:

class Thing1<A:SomeProtocol> {

    let variable: A

    init(variable: A) {
        self.variable = variable
        add1(variable)
    }

    func add1(stuff: A) {
        let thing = Thing2()
        thing.add2(stuff)
    }
}

Or, you could refactor your code to use the abstract type SomeProtocol, which will allow you to work with any type that conforms to SomeProtocol (e.g allowing you to mix different Thing1 instances with different variable types in an array):

class Thing1 {

    let variable: SomeProtocol

    init(variable: SomeProtocol) {
        self.variable = variable
        add1(variable)
    }

    func add1(stuff: SomeProtocol) {
        let thing = Thing2()
        thing.add2(stuff)
    }
}

class Thing2 {
    func add2(stuff: SomeProtocol) {

    }
}

Although you should always be aware of the extra costs that come with using abstract types, see this great WWDC talk for more info.

Community
  • 1
  • 1
Hamish
  • 78,605
  • 19
  • 187
  • 280
0

Adding the extensions to String and Int and constructing Thing1 objects makes it work:

extension String: SomeProtocol{}
extension Int: SomeProtocol{}

Thing1(variable: "a").add1("a")
Thing1(variable: 2).add1(4)
monchote
  • 3,440
  • 2
  • 20
  • 20