Default parameter as generic type

2020-03-24 04:36发布

问题:

I have protocol and his implementation written in Swift:

protocol P {
}

struct A: P {
}

Protocol is used as generic type for some function:

func foo<T: P>(param: T) {
}

func foo() {
    foo(param: A())
}

Until now everything works properly. But I would like to set A() as a default parameter of given function:

func foo<T: P>(param: T = A()) {
}

Unfortunately with following error:

Default argument value of type 'A' cannot be converted to type 'T'.

Or

func foo<T: P>(param: T = A() as P) {
}

,

let a: P = A()
func foo<T: P>(param: T = a) {
}

Returns:

Default argument value of type 'P' cannot be converted to type 'T'

Or

func foo<T: P>(param: T = A() as T) {
}

Returns:

'A' is not convertible to 'T'; did you mean to use 'as!' to force downcast?

What I'm doing wrong? Where is the problem?

I do not want to use force cast like this:

func foo<T: P>(param: T = A() as! T) {
}

Thank you in advance.

回答1:

You're trying to enforce a non-generic default argument in a generic function: you should probably think over what you're trying to achieve here.

For the sake of the discussion, you could include an attempted cast of A() to T in your function signature, but you'd need to change the argument type to optional to allow failed conversion (nil), e.g.

func foo<T: P>(param: T? = (A() as? T)) { }

A more sound alternative is including - in addition to your generic function - a concrete non-generic function for instances where T is A (concrete functions will take precedence over generic ones), in which case you can include the default argument of A() in the function signature of the concrete function. E.g.

protocol P { }
struct A: P { }
extension Int: P { }

func foo<T: P>(param: T) { print("called generic foo") }
func foo(param: A = A()) { print("called A specific foo") }

foo()    // called A specific foo (making use of default arg)
foo(A()) // called A specific foo
foo(1)   // called generic foo

Note that the non-generic foo is called even though A conforms to P (A could've made use of the generic foo): there's no conflict here as the concrete function takes precedence.


If you, on the other hand, just want your generic function to allow calling without the single argument (i.e., making use of a default argument), you can include a blueprint of a simple initializer in P, allowing you to initialize an instance of the generic type as default argument; see @Sulthan:s answer.



回答2:

The only thing you need is to add a requirement for an initializer to the protocol:

protocol P {
    init()
}

struct A: P {
    var x: Int

    init() {
        x = 10
    }
}

func foo<T: P>(param param: T = T()) {
}

However, you will have another problem. The type of the passed parameter decides the type of the generic so you will have to specify the generic type somehow else.