简体   繁体   中英

How to specialize protocol method on Swift Generic Type?

struct Point<T> {
    var x: T
    var y: T
}

extension Point: CustomStringConvertible  {
    var description: String {
        return "\(x), \(y)"
    }
}

// error: Conflicting conformance of 'Point<T>' to protocol 'CustomStringConvertible'; there cannot be more than one conformance, even with different conditional bounds
extension Point: CustomStringConvertible where T == Double{
    var description: String {
        return String(format: "%.3f, %.3f", x, y)
    }
}
print(Point(x: 1, y: 2)) // output: 1, 2
print(Point(x: Double(1.111111), y: Double(2.222222))) // expected output: 1.111, 2.222

How do I specialize the CustomStringConvertible.description: String method for Double type?

The compile error shows I cannot extend the same protocol twice.

Trial 1: If I removed the extending syntax : CustomStringConvertible , it compiles. But the Point<Double>.description will NOT be called when calling print(Point<Double>(...)) .

Trail 2: If I add a where T == Int at first extension, the same compile error shows.

What is the correct way to specialize extension method in Swift?

CustomStringConvertible is a protocol, and it requires a conforming class/struct to adopt a property called description .

The compiler shows you this error because your only one generic type struct Point tries to create/implement this required property of this protocol twice . You can't do that. Your struct conforms to CustomStringConvertible and it requires only 1 description property to be implemented.

So in your case, I would do the following:

在此处输入图像描述

Before description property returns something, it can have a basic if-else check. Let me know if this helped..!

Swift doesn't have the same concept of specialization as C++ does. Please see my very recent answer here that explains some differences to C++ template programming.

On the other hand, protocol specializations are only dispatched statically, so they would not work they way you want them, even if your code was allowed. Please see this answer here .

Swift relies on an more traditionally object-oriented approach, which features explicitly spelling out contracts by the means of protocols (which is not common in C++).

You want T to be convertible to a string according to your rules. So define a protocol for that:

protocol PointStringConvertible {
    var pointString: String { get }
}

struct Point<T: PointStringConvertible> {
    var x: T
    var y: T
}

Now implement PointStringConvertible for Double:

extension Double: PointStringConvertible {
    var pointString: String {
        String(format: "%.3f", self)
    }
}

Use your new protocol in Point:

extension Point: CustomStringConvertible  {
    var description: String {
        "\(x.pointString), \(y.pointString)"
    }
}

And it'll work:

print(Point(x: 12, y: 43))

prints 12.000, 43.000 .

However, this won't work for types that do not conform to your protocol:

print(Point(x: true, y: false))

will error. In my mind, that makes a lot of sense.

You can extend all types that conform CustomStringConvertible to easily implement PointStringConvertible for other types:

extension PointStringConvertible where Self: CustomStringConvertible {
    var pointString: String {
        description
    }
}

extension Bool: PointStringConvertible {}

But of course, this still requires declaring conformance, which is not a bad thing. If you don't like that, you can still do this:

struct Point<T> {
    var x: T
    var y: T
}

extension Double: PointStringConvertible {
    var pointString: String {
        String(format: "A double %.3f", self)
    }
}

extension Point: CustomStringConvertible  {
    private func pointString(for val: T) -> String {
        (val as? PointStringConvertible)?.pointString ?? "\(val)"
    }

    var description: String {
        "\(pointString(for: x)), \(pointString(for: y))"
    }
}

print(Point(x: 12.0, y: 43.0))
print(Point(x: true, y: false))

Giving:

A double 12.000, A double 43.000
true, false

Whether this is good design is another question. Note that you could, of course, also simply switch upon the type of T:

struct Point<T> {
    var x: T
    var y: T
}

extension Point: CustomStringConvertible  {
    private func pointString(for val: T) -> String {
        switch val {
        case let double as Double:
            return String(format: "A double %.3f", double)
        default:
            return "\(val)"
        }
    }

    var description: String {
        "\(pointString(for: x)), \(pointString(for: y))"
    }
}

print(Point(x: 12.0, y: 43.0))
print(Point(x: true, y: false))

But this probably constitutes knowledge of Double in Point.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM