带有数字文字的意外泛型类型推断行为

问题描述 投票:0回答:3

我正在尝试为 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
可能有问题?

swift generics swift-protocols
3个回答
0
投票

为什么编译器会推断出简单的

Double
文字 (
Value(1.0)
) 而不是
Int
文字 (
Value(1)
) 的类型?

我认为是因为

SignedNumeric
继承自
ExpressibleByIntegerLiteral
但它没有继承自
ExpressibleByFloatLiteral
.

当编译器看到带有小数点的文字时,例如

1.5
,除非可以从上下文中推断出浮点类型,否则它会假设
Double
然后它可以说
Scalar
在这种情况下必须是
Double
。这是一个特例,因此人们不必明确说明
f
中的
let f = 1.5
的类型。

整数文字有类似的规则,但不幸的是,它不适用,因为任何

ExpressibleByIntegerLiteral
都会覆盖该规则,即使它没有覆盖,如果您有两个
Scalar
类型,其
ExpressibleByIntegerLiteral
的实现使用了
 int
作为关联类型,您无法在它们之间进行选择。

至少,这是我的理论。


0
投票

如果你看

ExpressibleByFloatLiteral.FloatLiteralType

讨论

FloatLiteralType 的有效类型是 Float、Double 和 Float80(如果可用)。

但是对于

ExpressibleByIntegerLiteral.IntegerLiteralType
你有:

讨论

标准库整数和浮点类型都是 IntegerLiteralType 的有效类型。

即。当你从一个字面量初始化一个浮点数时,它只能是一个浮点数,默认是

Double
,但是当你使用一个整型字面量时,它既可以是整数也可以是浮点数,编译器会混淆。


0
投票

其实比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有关

© www.soinside.com 2019 - 2024. All rights reserved.