【问题标题】:How can I instantiate a new pointer of type argument with generic Go?如何使用通用 Go 实例化一个新的类型参数指针?
【发布时间】:2021-10-14 15:09:03
【问题描述】:

现在golang/go:master 上提供了类型参数,我决定试一试。我似乎遇到了在Type Parameters Proposal 中找不到的限制。 (或者我一定错过了)。

我想编写一个函数,它返回具有接口类型约束的泛型类型值的切片。如果传递的类型是一个带有指针接收器的实现,我们如何实例化它?

type SetGetter[V any] interface {
    Set(V)
    Get() V
}

// SetGetterSlice turns a slice of type V into a slice of type T,
// with T.Set() called for each entry in values.
func SetGetterSlice[V any, T SetGetter[V]](values []V) []T {
    out := make([]T, len(values))

    for i, v := range values {
        out[i].Set(v) // panic if T has pointer receiver!
    }

    return out
}

当以*Count 类型为T 调用上述SetGetterSlice() 函数时,此代码将在调用Set(v) 时出现恐慌。 (Go2go playground) 毫不奇怪,因为基本上代码创建了nil 指针的片段:


// Count implements SetGetter interface
type Count struct {
    x int
}

func (c *Count) Set(x int) { c.x = x }
func (c *Count) Get() int  { return c.x }

func main() {
    ints := []int{1, 2, 3, 4, 5}

    sgs := SetGetterSlice[int, *Count](ints)
    
    for _, s := range sgs {
        fmt.Println(s.Get())
    }
}

同一个问题的变种

这个想法行不通,我似乎找不到任何简单的方法来实例化指向的值。

  1. out[i] = new(T) 将产生一个compile failure,因为它返回一个*T,类型检查器希望在其中看到T
  2. 调用*new(T),编译但会产生相同的runtime panic,因为在这种情况下new(T)返回**Count,指向Count的指针仍然是nil
  3. 将返回类型更改为指向T 的指针切片将导致compile failure
func SetGetterSlice[V any, T SetGetter[V]](values []V) []*T {
    out := make([]*T, len(values))

    for i, v := range values {
        out[i] = new(T)
        out[i].Set(v) // panic if T has pointer receiver
    }

    return out
}

func main() {
    ints := []int{1, 2, 3, 4, 5}

    SetGetterSlice[int, Count](ints)
    // Count does not satisfy SetGetter[V]: wrong method signature
}

解决方法

到目前为止,我发现的唯一解决方案是要求将 constructor function 传递给通用函数。但这只是感觉不对,而且有点乏味。如果func F(T interface{})() []T 是完全有效的语法,为什么还要这样做?

func SetGetterSlice[V any, T SetGetter[V]](values []V, constructor func() T) []T {
    out := make([]T, len(values))

    for i, v := range values {
        out[i] = constructor()
        out[i].Set(v)
    }

    return out
}

// ...
func main() {
    ints := []int{1, 2, 3, 4, 5}

    SetGetterSlice[int, *Count](ints, func() *Count { return new(Count) })
}

总结

我的问题,按优先顺序排列:

  1. 我是否忽略了一些明显的东西?
  2. 这是 Go 中泛型的限制吗?这已经是最好的了?
  3. 这个限制是已知的还是我应该在 Go 项目中提出问题?

【问题讨论】:

    标签: go generics


    【解决方案1】:

    基本上,您必须向SetGetter 添加一个类型参数来限制T,以便稍后您可以将其转换为指针。

    你的约束已经声明了一个类型参数V,所以我们稍微修改一下上面的例子:

    // V is your original type param
    // T is the additional helper param
    type SetGetter[V any, T any] interface {
        Set(V)
        Get() V
        *T
    }
    

    然后用类型参数T any定义SetGetterSlice函数,其目的只是实例化约束SetGetter

    然后您就可以将表达式&out[i] 转换为指针类型,并成功调用指针接收器上的方法:

    // T is the type with methods with pointer receiver
    // PT is the SetGetter constraint with *T
    func SetGetterSlice[V any, T any, PT SetGetter[V, T]](values []V) []T {
        out := make([]T, len(values))
    
        for i, v := range values {
            // out[i] has type T
            // &out[i] has type *T
            // PT constraint includes *T
            p := PT(&out[i]) // valid conversion!
            p.Set(v)         // calling with non-nil pointer receiver
        }
    
        return out
    }
    

    完整程序:

    package main
    
    import (
        "fmt"
    )
    
    type SetGetter[V any, T any] interface {
        Set(V)
        Get() V
        *T
    }
    
    func SetGetterSlice[V any, T any, PT SetGetter[V, T]](values []V) []T {
        out := make([]T, len(values))
    
        for i, v := range values {
            p := PT(&out[i])
            p.Set(v)
        }
    
        return out
    }
    
    // Count implements SetGetter interface
    type Count struct {
        x int
    }
    
    func (c *Count) Set(x int) { c.x = x }
    func (c *Count) Get() int  { return c.x }
    
    func main() {
        ints := []int{1, 2, 3, 4, 5}
    
        // instantiate with base type
        sgs := SetGetterSlice[int, Count](ints)
    
        for _, s := range sgs {
            fmt.Println(s.Get()) // prints 1,2,3,4,5 each in a newline
        }
    }
    

    这变得更加冗长,因为SetGetterSlice 现在需要三个类型参数:原始V 加上T(带有指针接收器的类型)和PT(新约束)。但是,当您调用该函数时,您可以省略第三个 - 使用类型推断,实例化 PT SetGetter[V,T] 所需的类型参数 VT 都是已知的:

    SetGetterSlice[int, Count](ints)
    

    游乐场:https://go.dev/play/p/gcQZnw07Wp3

    【讨论】:

      【解决方案2】:

      您也可以尝试稍微不同地解决问题,以保持简单。

      package main
      
      import (
          "fmt"
      )
      
      func mapp[T any, V any](s []T, h func(T) V) []V {
          z := make([]V, len(s))
          for i, v := range s {
              z[i] = h(v)
          }
          return z
      }
      
      func mappp[T any, V any](s []T, h func(T) V) []V {
          z := make([]V, 0, len(s))
          for _, v := range s {
              z = append(z, h(v))
          }
          return z
      }
      
      // Count implements SetGetter interface
      type Count struct {
          x int
      }
      
      func (c *Count) Set(x int) { c.x = x }
      func (c *Count) Get() int  { return c.x }
      
      func FromInt(x int) *Count {
          var out Count
          out.x = x
          return &out
      }
      
      func main() {
          ints := []int{1, 2, 3, 4, 5}
      
          sgs := mapp(ints, FromInt)
          fmt.Printf("%T\n",sgs)
      
          for _, s := range sgs {
              fmt.Println(s.Get())
          }
      
          fmt.Println()
      
          sgs = mappp(ints, FromInt)
          fmt.Printf("%T\n",sgs)
      
          for _, s := range sgs {
              fmt.Println(s.Get())
          }
      }
      

      https://go2goplay.golang.org/p/vzViKwiJJkZ

      它就像你的func SetGetterSlice[V any, T SetGetter[V]](values []V, constructor func() T) []T,但没有复杂的冗长。它也给了我零痛苦来解决。

      【讨论】:

        【解决方案3】:

        编辑:参见blackgreen's answer,我后来在浏览他们链接的相同文档时也自己发现了它。我打算编辑此答案以基于此进行更新,但现在我不必这样做。 :-)

        可能有更好的方法——这个方法看起来有点笨拙——但我可以通过reflect 解决这个问题:

        if reflect.TypeOf(out[0]).Kind() == reflect.Ptr {
            x := reflect.ValueOf(out).Index(i)
            x.Set(reflect.New(reflect.TypeOf(out[0]).Elem()))
        }
        

        我刚刚将以上四行添加到您的示例中。临时变量是一些调试遗留下来的,显然可以删除。 Playground link

        【讨论】:

          猜你喜欢
          • 2022-11-18
          • 2015-03-19
          • 2017-01-02
          • 2012-03-14
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-12-23
          相关资源
          最近更新 更多