Parameter pack type as generic parameter

Hi all,

I'm trying to use parameter packs to parameterize sorting over keypaths to comparable fields, but I'm unable to work around an error diagnostic ("type of expression is ambiguous without more context").

It's unclear to me how to say the Comparable's vary, but the T does not. Here's what I'm doing:

func less<T, C: Comparable, each E: KeyPath<T, C>> (
    lhs: T, rhs: T, paths: repeat each E)  -> Bool? { ... }

Same result with each C:

private static func less<T,  each C: Comparable> (
  lhs: T, rhs: T, paths: repeat KeyPath<T, each C>)  -> Bool? { ... }

Putting the constraints in where-clause didn't help, nor did reasonable variants of ().

The code below should be complete for repro purposes.

Thoughts?

Thanks!

P.S. - fyi, the github swift Issue tracker has a label "pack expansion" which seems too narrow, but no "parameter pack" even though there are many labels found on searching "parameter".

swift-driver: 1.82.2 [...] swiftlang-5.9.0.114.6 clang-1500.0.27.1, Sonoma SDK

public struct LessDemo {
  public let i: Int
  public let c: Character

  // Caller
  public static func lessByChar(l: Self, r: Self) -> Bool {
    // Be explicit about types for demonstration purposes
    let sc: KeyPath<LessDemo, Character> = \LessDemo.c
    let si: KeyPath<LessDemo, Int> = \LessDemo.i

    // Diagnostic: type is ambiguous without more context
    let b: Bool? = less(lhs: l, rhs: r, paths: sc, si)
    return b ?? false
  }

  // Callee
  private static func less<T, C: Comparable, each E: KeyPath<T, C>> (
    lhs: T, rhs: T, paths: repeat each E)  -> Bool? {
    var result: Bool? = nil

    repeat _less(result: &result, each paths, lhs: lhs, rhs: rhs)

    return result
  }

  // Utility accepting inout to reduce result
  private static func _less<T, C:Comparable, K: KeyPath<T, C>>(
    result: inout Bool?, _ keyPath: KeyPath<T, C>, lhs: T, rhs: T) {
    guard nil == result else { return } // short-circuit
    let left = lhs[keyPath: keyPath]
    let right = rhs[keyPath: keyPath]
    result = left < right ? true : (left > right ? false : nil)
  }

  /// demo entrypoint (to build out)
  public static func demo() -> String {
    var many = Self.MANY
    many.sort(by: Self.sortChar)
    return "\(many.first ?? MANY[0])"
  }

  private static let KEYS
    = "qwertyuiopasdfghjklzxcvbnmdupshere"
  public static let MANY
    = (KEYS.enumerated()).map{ Self(i: $0.0, c: $0.1) }
}

The each C approach should work: Compiler Explorer (your code sample was missing LessDemo.sortChar, I added an always-returning-true one to let the code compile).

1 Like

Thanks for the quick reply. I hadn't thought to try the nightly build, where it indeed works!

For the record, the solution:

For Do
type parameter (pack) less<T, each C: Comparable>(...)
parameter declaration (..., paths: repeat KeyPath<T, each C>, ...)
value call repeat _less(..., each paths, ...)
Normal callee _less<T, C: Comparable>(.., _ keyPath: KeyPath<T, C>, ...)

Code below...

public struct LessDemo2 {

public let i: Int
public let c: Character

public static func sortChar(l: Self, r: Self) -> Bool {
  less(lhs: l, rhs: r, paths: \Self.c, \Self.i) ?? false
}

private static func less<T, each C: Comparable> (
    lhs: T, rhs: T, paths: repeat KeyPath<T, each C>
) -> Bool? {
    var result: Bool? = nil
    // Error here in 5.9 XCode-beta, but not nightly
    repeat _less(result: &result, each paths, lhs: lhs, rhs: rhs)
    return result
}
private static func _less<T, C: Comparable>(
  result: inout Bool?, _ keyPath: KeyPath<T, C>, lhs: T, rhs: T
) {
  guard nil == result else { return } // short-circuit
  let left = lhs[keyPath: keyPath]
  let right = rhs[keyPath: keyPath]
  result = left < right ? true : (left > right ? false : nil)
}

public static func demo() -> String {
    var many = Self.MANY
    many.sort(by: Self.sortChar)
    return "\(many.first ?? MANY[0])"
}

private static let KEYS = "qwertyuiopasdfghjklzxcvbnmdupshere"
public static let MANY = KEYS.enumerated().map {
    Self(i: $0.0, c: $0.1)
}

}