Assigning Tuples of Associated Types, Why Only Allowed via Explicit Member-By-Member Assignment? (Error: Cannot Express Tuple Conversion)

Accessing a member of a tuple

The problem with your code is that you are defining, (Int, String?, String?) to (statusCode: Int, message: String, errorMessage: String) which are interchangeable.

If you leave the type definition empty for the variable, the type inference should provide you with the correct type.

let response = (statusCode: 255, message: "Welcome", errorMessage: "Error")
print(response.message)

Or, if you want to define the type make sure that the type is actually (statusCode: Int, message: String, errorMessage: String).

let response: (statusCode: Int, message: String, errorMessage: String) = (statusCode: 255, message: "Welcome", errorMessage: "Error")
print(response.message)

You could also use tuple de-structuring syntax,

let (_, message, _) = (statusCode: 255, message: "Welcome", errorMessage: "Error")
print(message)

Swift tuple to Optional assignment

Another answer suggested (before it was changed) to just do:

(self.status!, self.message!) = self.parseResponse(resultBody)

I have found that is unsafe. It will crash if either self.status or self.message is nil at the time of the assignment. Try this test in a Playground:

class Test {
var status: Int?
var error: String?

func parseResponse() -> (Status:Int, Error:String)
{
return (200, "Success")
}

func testIt() {
(self.status!, self.error!) = parseResponse()
print(self.status)
print(self.error)
}
}

let mytest = Test()
mytest.testIt()

Here is another way it could be done:

let (stat, err) = self.parseResponse(resultBody)
(self.status, self.error) = (Optional(stat), Optional(err))

or, as @AndriyGordiychuk discovered, it works without Optional:

let (stat, err) = self.parseResponse(resultBody)
(self.status, self.error) = (stat, err)

It's curious that that works, but assigning the result of the function does not.


Note the following experiment:

var i: Int?
var s: String?

// This works
(i, s) = (3, "hello")

// This fails with error: cannot express tuple conversion '(Int, String)' to '(Int?, String?)
let t = (3, "hello")
(i, s) = t

It seems that when the assignment is a tuple literal assigned to a tuple, Swift takes a shortcut and doesn't first construct the tuple. Instead, is just assigns the individual elements.

So this:

(i, s) = (3, "hello")

is equivalent to:

i = 3
s = "hello"

which works because you can assign an Int to an Int? variable and a String to a String? variable. The tuple assignment fails because the types need to match.

Why is it valid to assign to an empty list but not to an empty tuple?

The comment by @user2357112 that this seems to be coincidence appears to be correct. The relevant part of the Python source code is in Python/ast.c:

switch (e->kind) {
# several cases snipped
case List_kind:
e->v.List.ctx = ctx;
s = e->v.List.elts;
break;
case Tuple_kind:
if (asdl_seq_LEN(e->v.Tuple.elts)) {
e->v.Tuple.ctx = ctx;
s = e->v.Tuple.elts;
}
else {
expr_name = "()";
}
break;
# several more cases snipped
}
/* Check for error string set by switch */
if (expr_name) {
char buf[300];
PyOS_snprintf(buf, sizeof(buf),
"can't %s %s",
ctx == Store ? "assign to" : "delete",
expr_name);
return ast_error(c, n, buf);
}

tuples have an explicit check that the length is not zero and raise an error when it is. lists do not have any such check, so there's no exception raised.

I don't see any particular reason for allowing assignment to an empty list when it is an error to assign to an empty tuple, but perhaps there's some special case that I'm not considering. I'd suggest that this is probably a (trivial) bug and that the behaviors should be the same for both types.

Implicit conversion from UnsafeBufferPointer to UnsafeRawBufferPointer

You cannot cast a UnsafeBufferPointer to a UnsafeRawBufferPointer
because that is more than reinterpreting the pointer: It requires
to calculate the raw byte count.

But you can create a UnsafeRawBufferPointer from a UnsafeBufferPointer:

withUnsafePointer(to: &x) { (ptr) in
let buff = UnsafeBufferPointer(start: ptr, count: 1)
let rawBuff = UnsafeRawBufferPointer.init(buff)
printBuffer(address: rawBuff, as: Int.self)
}

Here is the implementation of that initializer in UnsafeRawBufferPointer.swift.gyb:

  /// Creates a raw buffer over the contiguous bytes in the given typed buffer.
///
/// - Parameter buffer: The typed buffer to convert to a raw buffer. The
/// buffer's type `T` must be a trivial type.
@_inlineable
public init<T>(_ buffer: UnsafeMutableBufferPointer<T>) {
self.init(start: buffer.baseAddress!,
count: buffer.count * MemoryLayout<T>.stride)
}

Make custom type tie-able (compatible with std::tie)

Why the current attempts fail

std::tie(a, b) produces a std::tuple<int&, string&>.
This type is not related to std::tuple<int, string> etc.

std::tuple<T...>s have several assignment-operators:

  • A default assignment-operator, that takes a std::tuple<T...>
  • A tuple-converting assignment-operator template with a type parameter pack U..., that takes a std::tuple<U...>
  • A pair-converting assignment-operator template with two type parameters U1, U2, that takes a std::pair<U1, U2>

For those three versions exist copy- and move-variants; add either a const& or a && to the types they take.

The assignment-operator templates have to deduce their template arguments from the function argument type (i.e. of the type of the RHS of the assignment-expression).

Without a conversion operator in Foo, none of those assignment-operators are viable for std::tie(a,b) = foo.
If you add a conversion operator to Foo,
then only the default assignment-operator becomes viable:
Template type deduction does not take user-defined conversions into account.
That is, you cannot deduce template arguments for the assignment-operator templates from the type Foo.

Since only one user-defined conversion is allowed in an implicit conversion sequence, the type the conversion operator converts to must match the type of the default assignment operator exactly. That is, it must use the exact same tuple element types as the result of std::tie.

To support conversions of the element types (e.g. assignment of Foo::a to a long), the conversion operator of Foo has to be a template:

struct Foo {
int a;
string b;
template<typename T, typename U>
operator std::tuple<T, U>();
};

However, the element types of std::tie are references.
Since you should not return a reference to a temporary,
the options for conversions inside the operator template are quite limited
(heap, type punning, static, thread local, etc).

Why can't Java convert an ArrayListTreeSetInteger into a ListSetObject?

A List<Integer> is not a List<Object>. If Java allowed that, then you could call the method with List<String> and you will be broken. As Jeremy Heiler pointed out you can use List<? extends Object> and you will be fine. This means every type which extends Object is allowed. ? is called a wildcard in generic jargon.



Related Topics



Leave a reply



Submit