A Sequence
doesn’t necessarily have a consistent ordering. It has only whatever comes next.
Your Swift 5.6 examples concern protocols/existentials, it's not related to generics, except indirectly.
The pitch says awaiting implementation
. Is someone yet to start implementing it, or is there a WIP implementation that I could play around with? I don't mind how experimental, incomplete or dodgy the implementation is.
The implementation is on pause. Some of the type-checking is in main
, but I think it doesn't match the current pitch, and it definitely doesn't match what we intend to propose. More importantly, it's just the type-checking; there's no code generation at all. We do have some idea of what the underlying ABI will be, though.
Ok, thanks. Is there some sort of public timeline for it? Or is it more of a far away feature?
We’d like it as soon as possible, but I can safely say it won’t be in 5.7.
I just realized this works today (Swift 5.6)
func variadic(_ values: (any CustomStringConvertible)...) {
for value in values {
use(value)
}
}
func use(_ value: some CustomStringConvertible) {
print(String(describing: value), type(of: value))
}
variadic(1, "string", true)
prints
1 Int
string String
true Bool
It might work for some of the examples that are brought up in the Motivation section of the original proposal.
That doesn't seem to work on my machine.
I'm using Xcode 14 beta. Must be Swift 5.7 then
I don't see how it would, either: you're erasing the type information in the first function, then magically reading it in the second.
That magic is SE-0352
Yeah that’s 5.7 stuff.
What surprised me is that the runtime picked the first (generic) use
function to print the value, even though I had overrides with more specific, statically typed variants.
func use(_ value: some CustomStringConvertible) {
print(String(describing: value), type(of: value))
}
func use(_ value: String) {
print(value, "it's a String")
}
func use(_ value: Int) {
print(value, "it's an Int")
}
func use(_ value: Bool) {
print(value, "it's a Bool")
}
That is how Swift generics always work. Overloads (and protocol requirement implementations) are resolved based on the type information locally available at the use site; they're not like C++ templates where the code is instantiated with the type argument substituted in and then everything is re-resolved with that knowledge.
Function specializations are resolved at compile-time, so if a type of the value passed is unknown at the compile-time, generic function would be used.
So you can't specialize generic functions for any
types. Instead, you could define desired specializations by using protocols.
protocol P: CustomStringConvertible {
func use()
}
extension P {
func use() {
print(String(describing: self), type(of: self))
}
}
extension Int: P {
func use() {
print(self, "it's an Int")
}
}
(0).use() // 0 it's an Int
(0 as P).use() // 0 it's an Int
In this aspect they're exactly like C++ templates – those are also resolved at compile-time so specializations won't be called there as well.
struct A {
};
struct B : public A {
};
template<typename T>
void use(T value) {
std::cout << "generic function" << std::endl;
}
void use(B value) {
std::cout << "special case" << std::endl;
}
int main(int argc, const char * argv[]) {
use(B()); // special case
use((A) B()); // generic function
return 0;
}
"Compile time" is not an adequate summary here. Swift generics resolve based on the static type information available to the generic definition. C++ templates resolve based on the static type information available to the template after the substitution of template arguments, which are (when inferred for a function template) taken from the actual static types of the arguments. Those are very different behaviors; for example, Laszlo's example would call the concrete overloads of use
if Swift used the C++ model.
You're right that the C++ model is still based on static type information; if an expression produces a pointer or reference to a base class of the true dynamic type, C++ will resolve overloads and infer template arguments based on that base class and not on the dynamic type. Accuracy demands that I point out that that's not what's happening in your example, though; the argument here is an object slice produced by copying the A
base subobject out of the temporary B
, and its dynamic type truly is A
.
Would it? I think that's only true if the variadic
function actually used variadic generics as pitched. By using the any CustomStringConvertible
the static type information link is broken—even if Swift used a C++-style model it seems to me like the generic use
declaration would end up being called, rather than the specialized overloads. But we're talking hypotheticals on hypotheticals at that point.
You're right, I was mentally mapping it over to a variadic template. C++ doesn't have any way to declare a function that takes a variadic number of concretely-typed arguments, so you would have to do something different here.
Can you provide a link (for example, a post in the forum) or give a quick explanation about why it was decided for Swift generics to work this way? I have a general knowledge about the difference, but I never quite explored the reasoning behind it.