SwiftのGenericsとwhere句をC++テンプレート感覚で読む

SwiftにもGenericsがあります。

C++のテンプレートを知っていると入りやすいですが、SwiftのGenericsはprotocol制約とセットで読むことが多いです。

公式では、Genericsは型に依存しない柔軟で再利用可能な関数や型を書くための機能として説明されています。

https://docs.swift.org/swift-book/documentation/the-swift-programming-language/generics/

generic function

func identity<T>(_ value: T) -> T {
    value
}

使う側は型推論されます。

let a = identity(10)
let b = identity("abc")

C++ならこういうテンプレート関数に近いです。

template <class T>
T identity(T value) {
    return value;
}

型制約を書く

Swiftではprotocolで型制約を書きます。

func printAll<T: Sequence>(_ values: T) {
    for value in values {
        print(value)
    }
}

TSequence に準拠している必要があります。

C++20のconceptsに近い読み方ができます。

template <std::ranges::range R>
void printAll(R values) {
    ...
}

where句

条件が複雑になると where を使います。

func containsSameElements<S1: Sequence, S2: Sequence>(
    _ lhs: S1,
    _ rhs: S2
) -> Bool where S1.Element == S2.Element, S1.Element: Equatable {
    Array(lhs) == Array(rhs)
}

where には、関連型の条件や準拠条件を書きます。

最初は少し読みにくいですが、「Tの中にあるElementにも条件を付けている」と見ると整理しやすいです。

protocolのassociatedtypeと絡む

SwiftのGenericsでよく出るのが associatedtype です。

protocol Repository {
    associatedtype Item

    func find(id: String) -> Item?
}

準拠する型が Item を決めます。

struct UserRepository: Repository {
    func find(id: String) -> User? {
        ...
    }
}

C++のテンプレートやtraits的なものを知っていると、少し見通しがよくなります。

まとめ

SwiftのGenericsは、C++テンプレートに似ていますが、protocol制約と一緒に読むことが多いです。

  • <T> で型パラメータを書く
  • T: Protocol で制約を書く
  • 複雑な制約は where
  • protocolの associatedtype と絡むことが多い

C++のテンプレートより、呼び出し側で派手に特殊化するというより、型安全なAPIを書くための道具として見ると扱いやすいです。

関連記事

参考