digitalmars.D.learn - CommonType and non-built-in types
- Joseph Rushton Wakeling (37/37) Oct 01 2013 Hello all,
- John Colvin (16/44) Oct 01 2013 This contains quite a bit of trickery. A ternary expression must
-
Joseph Rushton Wakeling
(7/10)
Oct 02 2013
Ohh!
It's saying "U is the type of the expression (true? T... - Dicebot (20/37) Oct 01 2013 a) Ternary operator returns value of type that can be used to
- Dicebot (3/3) Oct 01 2013 P.S. adding implicit conversion other way around is possible via
- monarch_dodra (27/81) Oct 01 2013 The code basically uses operator ?: which is basically:
- Joseph Rushton Wakeling (12/31) Oct 02 2013 Yea, I think I had in my head "This is generic/static/compile time/templ...
Hello all, In the course of examining std.rational I've had to take a look inside std.traits.CommonType, and I'm hoping people can help me to understand some fine details which I'm currently unsure of. The essence of the CommonType template is simple: * If it is passed no arguments, the common type is void. * If it is passed one argument, the common type is the type of that argument. * If it is passed more than one argument, it looks for the common type U between the first 2 arguments. If it finds it, then it returns the common type of U and the remaining arguments (in other words, it recursively identifies the common types of successive arguments until none are left). * If the first 2 arguments can't be implicitly converted, it returns void. A consequence of this is that CommonType will not necessarily work nicely with many non-built-in types. For example, the common type of BigInt and int is void, even though in principle it should be possible to convert an int to a BigInt. It's this that is particularly of concern to me. Anyway, to concrete questions. (1) Can someone please explain to me _in detail_ the mechanics of the code which identifies whether the first 2 template arguments have a common type? I understand what it does, but not why/how it does it, if you get me :-) static if (is(typeof(true ? T[0].init : T[1].init) U)) { alias CommonType!(U, T[2 .. $]) CommonType; } (2) Same code -- why is it only necessary to check T[0].init : T[1].init and not vice versa? (Yes, you can tell I don't really understand the : operator properly:-) (3) What would one have to implement in a library-defined type to enable T[0].init : T[1].init to evaluate to true? For example, to enable int and BigInt to be compatible? (4) Is there a good reason why there _shouldn't_ be a CommonType of (say) int and BigInt? I'm sure I'll think up more questions, but this seems enough to be going on with ... :-) Thanks & best wishes, -- Joe
Oct 01 2013
On Tuesday, 1 October 2013 at 10:50:39 UTC, Joseph Rushton Wakeling wrote:(1) Can someone please explain to me _in detail_ the mechanics of the code which identifies whether the first 2 template arguments have a common type? I understand what it does, but not why/how it does it, if you get me :-) static if (is(typeof(true ? T[0].init : T[1].init) U)) { alias CommonType!(U, T[2 .. $]) CommonType; } (2) Same code -- why is it only necessary to check T[0].init : T[1].init and not vice versa? (Yes, you can tell I don't really understand the : operator properly:-)This contains quite a bit of trickery. A ternary expression must evaluate to a single, statically known type. Therefore, true ? T[0].init : T[1].init will only be a valid expression if there is a common type between T[0] and T[1] From the spec dlang.org/expression.html:Conditional Expressions ConditionalExpression: OrOrExpression OrOrExpression ? Expression : ConditionalExpression The first expression is converted to bool, and is evaluated. If it is true, then the second expression is evaluated, and its result is the result of the conditional expression. If it is false, then the third expression is evaluated, and its result is the result of the conditional expression. If either the second or third expressions are of type void, then the resulting type is void. Otherwise, the second and third expressions are implicitly converted to a common type which becomes the result type of the conditional expression.If there is a common type then U is declared as an alias of it and the is expression returns true. If there isn't a common type, the ternary expression is an error and the is expression will return false. In the first case we just recursively traverse the type list, in the second we declare the common type to be void. Basically, it's just a wrapper around some compiler magic. All the common type calculation is all done by the compiler as a convenient effect of the ternary operator.
Oct 01 2013
On 01/10/13 13:13, John Colvin wrote:This contains quite a bit of trickery. A ternary expression must evaluate to a single, statically known type. Therefore, true ? T[0].init : T[1].init will only be a valid expression if there is a common type between T[0] and T[1]Ohh! <light dawns>It's saying "U is the type of the expression (true? T[0] : T1) and if is(U), i.e. if U exists, then ..." And U will only exist if as you say there is a common type, which is inferred here.</light dawns> I'd misinterpreted the meaning of T[0] : T[1] completely. (More on that in my reply to monarchdodra.) Thanks very much!
Oct 02 2013
On Tuesday, 1 October 2013 at 10:50:39 UTC, Joseph Rushton Wakeling wrote:(1) Can someone please explain to me _in detail_ the mechanics of the code which identifies whether the first 2 template arguments have a common type? I understand what it does, but not why/how it does it, if you get me :-) static if (is(typeof(true ? T[0].init : T[1].init) U)) { alias CommonType!(U, T[2 .. $]) CommonType; }a) Ternary operator returns value of type that can be used to store values of both condition branches. Don't know exact algorithm in compiler do determine this but it is likely based on presence of implicit conversion. b) If it is impossible to use both types in ternary operator, no implicit conversions (and thus no common type) are possible. Thus it will result in compilation error and typeof() of whole expression will be invalid type, making is() return false. c) If it is a valid expression, its type is CommonType and aliased to U. Then template is recursively called to find common type between U and rest of template parameter list. d) In the end who;e CommonType template will be aliased to last such U in recursion chain.(2) Same code -- why is it only necessary to check T[0].init : T[1].init and not vice versa? (Yes, you can tell I don't really understand the : operator properly:-)It is not is(T : U) form but ternary operator.(3) What would one have to implement in a library-defined type to enable T[0].init : T[1].init to evaluate to true? For example, to enable int and BigInt to be compatible?It requires int to be implicitly convertible to BigInt. Don't know if we have tools to do it.(4) Is there a good reason why there _shouldn't_ be a CommonType of (say) int and BigInt?If it is possible to express it, common type of those should be BigInt.
Oct 01 2013
P.S. adding implicit conversion other way around is possible via `alias this` but I don't think it is a valid behavior. (as common type of (`long`, `int`, `short`) is `long`)
Oct 01 2013
On Tuesday, 1 October 2013 at 10:50:39 UTC, Joseph Rushton Wakeling wrote:Hello all, In the course of examining std.rational I've had to take a look inside std.traits.CommonType, and I'm hoping people can help me to understand some fine details which I'm currently unsure of. The essence of the CommonType template is simple: * If it is passed no arguments, the common type is void. * If it is passed one argument, the common type is the type of that argument. * If it is passed more than one argument, it looks for the common type U between the first 2 arguments. If it finds it, then it returns the common type of U and the remaining arguments (in other words, it recursively identifies the common types of successive arguments until none are left). * If the first 2 arguments can't be implicitly converted, it returns void. A consequence of this is that CommonType will not necessarily work nicely with many non-built-in types. For example, the common type of BigInt and int is void, even though in principle it should be possible to convert an int to a BigInt. It's this that is particularly of concern to me. Anyway, to concrete questions. (1) Can someone please explain to me _in detail_ the mechanics of the code which identifies whether the first 2 template arguments have a common type? I understand what it does, but not why/how it does it, if you get me :-) static if (is(typeof(true ? T[0].init : T[1].init) U)) { alias CommonType!(U, T[2 .. $]) CommonType; } (2) Same code -- why is it only necessary to check T[0].init : T[1].init and not vice versa? (Yes, you can tell I don't really understand the : operator properly:-) (3) What would one have to implement in a library-defined type to enable T[0].init : T[1].init to evaluate to true? For example, to enable int and BigInt to be compatible? (4) Is there a good reason why there _shouldn't_ be a CommonType of (say) int and BigInt? I'm sure I'll think up more questions, but this seems enough to be going on with ... :-) Thanks & best wishes, -- JoeThe code basically uses operator ?: which is basically: auto oeprator(T1, T2)(bool cont, T1 lhs, T2 rhs) { if (cond) return lhs; else return rhs; } By using the operator's return type, you get, basically, what the compiler believes is the "common type" that you'd get from either a T1, or a T2. Back to the code: static if (is(typeof(true ? T[0].init : T[1].init) U)) This basically checks if ternary compiles, and if it does, "assigns" the return type to U, after which, the common type becomes U.(2) Same code -- why is it only necessary to check T[0].init : T[1].init and not vice versa? (Yes, you can tell I don't really understand the : operator properly:-)Order makes no difference.(3) What would one have to implement in a library-defined type to enable T[0].init : T[1].init to evaluate to true?I think you are reading the code wrong, it's not "T[0].init : T[1].init" that evaluates to "true". It's the argument of the ternary operator. "true" is just a dummy placeholder. What this code is checking is that "condition ? T[0].init : T[1].init" compiles at all.(4) Is there a good reason why there _shouldn't_ be a CommonType of (say) int and BigInt?Well, given that D doesn't allow implicit construction, and that the entire point of "CommonType" (AFAIK) is to check the *implicit* common type, it would be a little difficult.
Oct 01 2013
On 01/10/13 13:15, monarch_dodra wrote:By using the operator's return type, you get, basically, what the compiler believes is the "common type" that you'd get from either a T1, or a T2. Back to the code: static if (is(typeof(true ? T[0].init : T[1].init) U)) This basically checks if ternary compiles, and if it does, "assigns" the return type to U, after which, the common type becomes U.Yea, I think I had in my head "This is generic/static/compile time/template stuff, and the only time I've ever seen A : B with respect to types is when you're attempting to indicate that A is implicitly convertible to B." I had a "D'oh!" moment when I realized shortly after sending the email that it was actually probably a condition ? ifTrue : ifFalse style expression, but still didn't get what that meant here or why it did what I did. Now that you and others have explained this, I'm impressed. It's a very nice example of how template code in D really is just like "real" code except that the variables are template parameters rather than variables.(2) Same code -- why is it only necessary to check T[0].init : T[1].init and not vice versa? (Yes, you can tell I don't really understand the : operator properly:-)Order makes no difference.(3) What would one have to implement in a library-defined type to enable T[0].init : T[1].init to evaluate to true?I think you are reading the code wrong, it's not "T[0].init : T[1].init" that evaluates to "true". It's the argument of the ternary operator. "true" is just a dummy placeholder. What this code is checking is that "condition ? T[0].init : T[1].init" compiles at all.Well, given that D doesn't allow implicit construction, and that the entire point of "CommonType" (AFAIK) is to check the *implicit* common type, it would be a little difficult.Ahh. OK, this makes things clear, and explain why David Simcha needed things like CommonRational and CommonInteger in his std.rational.
Oct 02 2013