以下函数定义是合法的Swift:
func doSomething<T: StringProtocol>(value: T = "abc") {
// ...
}
编译器能够确定默认参数"abc"
是String
,String
符合StringProtocol
。
但是这段代码不能编译:
func doSomething<T: Collection>(value: T = "abc") where T.Element == Character {
// ...
}
编译错误:
“String”类型的默认参数值无法转换为“T”类型
似乎编译器将拥有与第一种情况一样多的信息来确定String
确实可以转换为T
。此外,如果我删除默认参数并使用相同的值调用该函数,它的工作原理如下:
doSomething(value: "abc")
这个函数可以用不同的方式编写,以便我可以提供默认的String
参数吗?这是Swift的限制,还是仅限于我的心理模型?
重要的约束是T: ExpressibleByStringLiteral
。这是允许从字符串文字初始化的东西。
func doSomething<T: Collection>(value: T = "abc")
where T.Element == Character, T: ExpressibleByStringLiteral {
// ...
}
正如Leo Dabus指出的那样,T.Element == Character
在技术上并不是必需的,但删除它会改变其含义。仅仅因为某个东西是一个集合并且可以用字符串文字初始化并不意味着它的元素是字符。
同样值得注意的是,虽然所有这些都是可能的,但它通常是糟糕的Swift IMO。 Swift无法表达默认类型是什么,因此所有这些情况下的doSomething()
都会导致“无法推断出通用参数'T'”。
正确的解决方案IMO是一个过载,它避免了所有这些问题:
func doSomething<T: StringProtocol>(value: T) {
}
func doSomething() {
doSomething(value: "abc")
}
这允许你使默认参数不仅仅是“可以用文字"abc"
初始化的东西”,而是你真正的意思:默认值是字符串“abc”。
通常,默认参数只是重载的便利,因此通常可以使用缺少该参数的显式重载替换任何默认参数。