我正在尝试为 Swift 5.7 中的任何数值创建通用包装器(à la micrograd)。到目前为止,我有这个实现:
struct Value<Scalar: SignedNumeric>: Equatable {
var data: Scalar
init(_ data: Scalar) {
self.data = data
}
}
// MARK: Value+SignedNumeric
extension Value: SignedNumeric {
typealias IntegerLiteralType = Scalar.IntegerLiteralType
typealias Magnitude = Scalar.Magnitude
var magnitude: Magnitude {
data.magnitude
}
init(integerLiteral value: IntegerLiteralType) {
self.data = Scalar(integerLiteral: value)
}
init?<T>(exactly source: T) where T : BinaryInteger {
if let numericData = Scalar(exactly: source) {
self.data = numericData
} else {
return nil
}
}
static func * (lhs: Value<Scalar>, rhs: Value<Scalar>) -> Value<Scalar> {
return Value(lhs.data * rhs.data)
}
static func + (lhs: Value<Scalar>, rhs: Value<Scalar>) -> Value<Scalar> {
return Value(lhs.data + rhs.data)
}
static func - (lhs: Value<Scalar>, rhs: Value<Scalar>) -> Value<Scalar> {
return Value(lhs.data - rhs.data)
}
static func *= (lhs: inout Value<Scalar>, rhs: Value<Scalar>) {
lhs.data = lhs.data * rhs.data
}
}
我遇到的问题是,如果我只提供没有任何显式类型注释的整数文字,下面的代码将无法编译:
func testScalarTypeInference() {
// all of these compile fine
Value(Double(1.0))
Value<Double>(1)
Value<Int>(1)
Value(Int(1))
Value(1 as Int)
Value(1.0)
// doesn't compile
Value(1) // Generic parameter 'Scalar' could not be inferred in cast to 'Value'
}
为什么编译器会推断出简单的
Double
文字 (Value(1.0)
) 而不是 Int
文字 (Value(1)
) 的类型?
编辑:我忘了提到最令人困惑的部分。这个问题只存在于
Value
试图符合 SignedNumeric
的时候。以下代码编译正常:
struct ValueWrapper<Scalar: SignedNumeric> {
var data: Scalar
init(_ data: Scalar) {
self.data = data
}
}
func testValueWrapperTypeInference() {
type(of: ValueWrapper(1)) // Expression of type 'ValueWrapper<Int>.Type' is unused
type(of: ValueWrapper(1.0)) // Expression of type 'ValueWrapper<Double>.Type' is unused
}
所以我认为其中一个
init
可能有问题?
为什么编译器会推断出简单的
文字 (Double
) 而不是Value(1.0)
文字 (Int
) 的类型?Value(1)
SignedNumeric
继承自ExpressibleByIntegerLiteral
但它没有继承自ExpressibleByFloatLiteral
.
当编译器看到带有小数点的文字时,例如
1.5
,除非可以从上下文中推断出浮点类型,否则它会假设 Double
然后它可以说 Scalar
在这种情况下必须是 Double
。这是一个特例,因此人们不必明确说明 f
中的 let f = 1.5
的类型。
整数文字有类似的规则,但不幸的是,它不适用,因为任何
ExpressibleByIntegerLiteral
都会覆盖该规则,即使它没有覆盖,如果您有两个 Scalar
类型,其 ExpressibleByIntegerLiteral
的实现使用了 int
作为关联类型,您无法在它们之间进行选择。
至少,这是我的理论。
ExpressibleByFloatLiteral.FloatLiteralType
:
讨论
FloatLiteralType 的有效类型是 Float、Double 和 Float80(如果可用)。
ExpressibleByIntegerLiteral.IntegerLiteralType
你有:
讨论
标准库整数和浮点类型都是 IntegerLiteralType 的有效类型。
即。当你从一个字面量初始化一个浮点数时,它只能是一个浮点数,默认是
Double
,但是当你使用一个整型字面量时,它既可以是整数也可以是浮点数,编译器会混淆。
其实比SignedNumeric简单。 ExpressibleByIntegerLiteral(SignedNumeric 需要)也存在同样的问题:
struct Value<Scalar: ExpressibleByIntegerLiteral>: ExpressibleByIntegerLiteral {
var data: Scalar
init(integerLiteral value: Scalar.IntegerLiteralType) {
self.data = Scalar(integerLiteral: value)
}
}
Value(1) // Generic parameter 'Scalar' could not be inferred in cast to 'Value'
我认为不可能构建您想要构建的东西。 (ExpressibleByIntegerLiteral 上的
init(_ value: Self)
扩展名可能存在歧义,这可能就是为什么将其称为“cast”的原因。)如果您需要此功能,我建议打开 bug 报告。我不认为可以将 ExpressibleByIntegerLiteral 创建为通用包装器,因为您不能默认标量类型,这会阻止您符合 SignedNumeric。
可能与SR-7476有关