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,
-- Joe
The 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.