英文:
Generic type conversion in Golang
问题
我厌倦了手动编写像[]int32 -> []int64这样的切片转换,因为在许多情况下,您需要使用另一种类型的切片。所以我尝试编写一个通用函数来解决这个问题:
func convertSlice[T1 any, T2 any](t1 []T1) []T2 {
t2 := make([]T2, len(t1))
for i := range t1 {
t2[i] = T2(t1[i])
}
return t2
}
并且想要像这样使用它:
a := []int{1, 2, 3, 4, 5}
var b []int64 = convertSlice[int, int64](a)
但是我无法编译它,编译器报错:./prog.go:8:14: cannot convert t1[i] (variable of type T1 constrained by any) to type T2
那么,我该如何修复这个问题呢?
实时示例:https://go.dev/play/p/YYOLFjYt4mq
当然,我可以为每种原始类型编写单独的函数,例如:
func convertNumericSlice[T1, T2 constraints.Integer | constraints.Float](t1 []T1) []T2 {}
func convertStringSlice[T1, T2 ~string](t1 []T1) []T2 {}
但这看起来并不像一个很酷的通用方法。
英文:
I got tired to write manual slice conversions like []int32 -> []int64, because there are many situations when you need to do use slice with another type.
So I tried to write a generic function for that:
func convertSlice[T1 any, T2 any](t1 []T1) []T2 {
t2 := make([]T2, len(t1))
for i := range t1 {
t2[i] = T2(t1[i])
}
return t2
}
and want to use it like
a := []int{1, 2, 3, 4, 5}
var b []int64 = convertSlice[int, int64](a)
But I cant compile it, compiler says that ./prog.go:8:14: cannot convert t1[i] (variable of type T1 constrained by any) to type T2
So, how can I fix that?
Live example: https://go.dev/play/p/YYOLFjYt4mq
Of course, I can write separate functions for every primitive type like:
func convertNumericSlice[T1, T2 constraints.Integer | constraints.Float](t1 []T1) []T2 {}
func convertStringSlice[T1, T2 ~string](t1 []T1) []T2 {}
but that doesnt look like cool generic way at all.
答案1
得分: 1
如果您有一个包含所有数字类型的类型,那么可以在它们之间进行转换。
请尝试以下代码:
type Number interface {
int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64 | float32 | float64
}
func convertSlice[T1 Number, T2 Number](t1 []T1) []T2 {
t2 := make([]T2, len(t1))
for i := range t1 {
t2[i] = T2(t1[i])
}
return t2
}
英文:
If you have a type with all number types then it's possible to convert in between.
Try like below:
type Number interface {
int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64 | float32 | float64
}
func convertSlice[T1 Number, T2 Number](t1 []T1) []T2 {
t2 := make([]T2, len(t1))
for i := range t1 {
t2[i] = T2(t1[i])
}
return t2
}
答案2
得分: 1
无法将具有任意约束的类型参数进行转换。规范中提到了以下内容(转换):
[...] 如果满足以下条件之一,x 也可以转换为类型 T:
- V 和 T 都是类型参数,并且 V 的类型集中的每种类型都可以转换为 T 的类型集中的每种类型。
如果 T1
和 T2
都受到 any
的约束,那么两个类型集合实际上包含任何可能的类型。
尽管你的 convertSlice
函数的特定实例可能是有效的,但编译器无法静态地证明转换 T2(t1)
总是有效的。理论上,你可以实例化 convertSlice[string, chan func()]
,而字符串显然无法转换为函数通道。
编写一个通用函数的唯一方法是使用反射和 CanConvert
方法,但你需要决定如何处理 CanConvert
返回 false
的情况。抛出异常?返回零值?
如果选择使用泛型,那么你已经在选择类型安全性方面做出了选择,因此引入反射似乎是违背直觉的。使用泛型,你必须为每组可转换的基础类型编写函数。主要的特殊情况包括:
数字:
type Number interface {
constraints.Integer | constraints.Float
}
convertNumbers[T1, T2 Number](t1 []T1) []T2 {}
字符串 - 它对于字节切片和符文切片进行了特殊处理,但字节切片和符文切片之间不能相互转换,因此你需要在这里编写两个函数:
convertStrings[T1, T2 ~string | ~[]byte](t1 []T1) []T2 {}
// 或者
convertStrings[T1, T2 ~string | ~[]rune](t1 []T1) []T2 {}
复数:
convertComplex[T1, T2 ~complex64 | ~complex128](t1 []T1) []T2 {}
英文:
It is not possible to convert type parameters with arbitrary constraints. The spec mentions that (conversions):
> [...] x can also be converted to type T if one of the following conditions applies:
>
> - Both V and T are type parameters and a value of each type in V's type set can be converted to each type in T's type set.
If both T1
and T2
are constrained with any
, both type sets are virtually include any possible type.
Even though a particular instantiation of your convertSlice
function may be valid, the compiler can't statically prove that the conversion T2(t1)
is always valid. In theory you could instantiate convertSlice[string, chan func()]
and then a string obviously can't be converted to a channel of functions.
The only way to write a catch-all function is to use reflection with the CanConvert
method, but you would be left with deciding how to handle the case where CanConvert
returns false
. Panic? Return a zero value?
If you choose to use generics, you are already making a choice towards type safety, so introducing reflection seems counter-intuitive. With generics, you do have to write functions for each set of convertible underlying types. The main special cases are:
Numbers:
type Number interface {
constraints.Integer | constraints.Float
}
convertNumbers[T1, T2 Number](t1 []T1) []T2 {}
Strings — it is special-cased for byte slices and rune slices, but byte slices and rune slices aren't convertible to each other, so you'd need two functions here):
convertStrings[T1, T2 ~string | ~[]byte](t1 []T1) []T2 {}
// or
convertStrings[T1, T2 ~string | ~[]rune](t1 []T1) []T2 {}
Complex:
convertComplex[T1, T2 ~complex64 | ~complex128](t1 []T1) []T2 {}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论