🍣

Swiftで色々な型の可変長引数でinoutパラメータにする方法

2022/02/18に公開

swiftでGo言語のfmt.Scan、fmt.Fscan、fmt.Sscanみたいなのを書いてみました。
https://github.com/nnabeyang/scan-swift
そのとき可変長引数、型はバラバラで、なおかつinoutパラメータのように動く関数を書くのに苦労したので、その共有です。swiftの勉強として書いたものなので、標準的な解決方法は別にあるかもしれません。

解決方法

関数の定義は次の通りです。

public func sscan(content: String, _ a :Any...)throws -> Int

使う側はUnsafeMutablePointer<T>Anyにキャストして渡します。

func p<T>(_ x: UnsafeMutablePointer<T>) -> Any {
    return x
}
var (s, b, c) = ("", 0, 0)
let n = try sscan(content: "hello     2345  111", p(&s), p(&b), p(&c))

型はswitchで振り分けます。

    func doScan(_ args: [Any])throws -> Int {
        var n = 0
        for  arg in args {
            buf = []
            switch arg {
            case let v as UnsafeMutablePointer<Int>:
                v.pointee = try scanInt()
            case let v as UnsafeMutablePointer<String>:
                v.pointee = try convertString()
            default:
                throw NSError(domain: "unsupport type", code: -1, userInfo: nil)
            }
            n+=1
        }
        return n
    }

とりあえずこれで動きますが、できれば関数pみたいなの消したいです。

考えたこと

まず次のような感じに書くと'inout' must not be used on variadic parametersというエラーが出ます。

func sscan(_ str: String, _ a: inout Any...)throws -> Int

可変長引数にinoutは使えないんですよね。

次に、こんな感じにすれば良いかなとも思いますが、これだとIntとかStringは混ぜられないですよね。

func sscan<T>(_ str: String, _ a: inout [T])throws -> Int

そしたら実質inoutみたいに変更できて、どんな型でも混ぜられるUnsafeMutableRawPointerみたいなのどうかと思ったのですが、これだと引数で渡した変数の型が全く分からなくなります。

func sscan(_ str: String, _ a: UnsafeMutableRawPointer...)throws -> Int

ということで、型情報がありinout無しで変数変えるとなるとUnsafeMutablePointer<T>を使うことになり、いろいろな型を混ぜるとなるとAnyで渡すのかなということになりました。

Discussion