Jonathan M Davis 
Posted in reply to Luka Aleksic
| On Wednesday, June 27, 2018 16:19:56 Luka Aleksic via Digitalmars-d-learn wrote:
> Hello,
>
> In the following code:
>
> T first;
> U second;
>
> this(T arg_first, U arg_second) {
> first = arg_first;
> second = arg_second;
> }
> };
>
> void main() {
>
> pair!(char, uint) p1 = pair('a', 1);
>
> }
>
> I am getting the following error:
>
> scratch.d(14): Error: struct scratch.pair cannot deduce function
> from argument types !()(char, int), candidates are:
> scratch.d(2): scratch.pair(T, U)
> Failed: ["/usr/bin/dmd", "-v", "-o-", "scratch.d", "-I."]
>
> Changing the offending line to:
>
> pair!(char, uint) p1 = pair!(char, uint)('a', 1);
>
> fixes the issue.
>
> However I am interested to know why the first code couldn't deduce the types-- and why does it even have to try to deduce them, when I explicitly stated that p1 was of the type "pair of char and uint"?
>
> Thanks,
> L. Aleksic
Well, for one, what's on the left side of the = doesn't normally affect the type of what's on the right. It does in some cases with literals - e.g.
char c = 'a';
compiles just fine in spite of the fact that 'a' is a dchar, but if what's on the right-hand side is not a literal, then the type has to match or be implicitly convertible to the type of the variable it's initializing or being assigned to. And it's definitely not the case that any template instantitaions on the right-hand side get instantiated based on what's on the left. pair('a', 1) has to compile on its own and result in a type which implicitly converts to pair!(char, uint) for your code to work, and that's definitely not the case.
The other big issue here is that the only time that templates are ever implicitly instantiated is for functions - which is why it's called IFTI (implicit function template instantiation). Even something like
struct S(T = int)
{
}
S s;
would not compile, because S is a template. The code would have to use
S!() s;
or
S!int s;
S by itself is not a type. It's a template. This can be annoying at times, but it stems from the fact that you'd get various ambiguities otherwise. e.g. if S was implicitly instantiatied as S!int, then what would
alias Foo = S;
mean? It could be the template, or it could be the instantiation of the template. Because of that, implicit instantation never happens for types.
So, when the compiler sees pair('a', 1), there is no function named pair.
There is no constructor. There isn't even a type named pair.
pair!(char, uint) would be a type, or it would be a constructor, but pair is
just a template. So, when it sees
pair!(char, uint) p1 = pair('a', 1);
it sees you trying to call a function that doesn't exist. There is no function pair - not even a templated function named pair.
However, while there is ambiguity in implicitly instantiating templated types, for functions, there is no ambiguity (since the function call syntax is unambiguous). So, templated functions _can_ have their template arguments infered (hence IFTI). So, the typical solution to this sort of problem is to create a helper function. e.g.
struct Pair(T, U)
{
T first;
U second;
this(T arg_first, U arg_second)
{
first = arg_first;
second = arg_second;
}
}
auto pair(T, U)(T first, U second)
{
return Pair!(T, U)(first, second);
}
That way, you have a function which can take advantage of IFTI to infer the template arguments (what you'd do for naming in your case if you want to use camelCasing for types, I don't know, but normally, D code uses PascalCasing for types and camelCasing for functions, which makes the naming pretty straightforward in cases like this). Now, even then
Pair!(char, uint) p1 = pair('a', 1);
won't compile, because the literal 'a' defaults to dchar, and the literal 1, defaults to int. So, pair('a', 1) would have the type Pair!(dchar, int). 1u could be used to turn 1 into a uint literal, but you'd have to use a cast to force 'a' to be a char. e.g.
Pair!(char, uint) p1 = pair(cast(char)'a', 1u);
Now, normally, you also wouldn't put the type on the left-hand side of a variable declaration like that. You'd just use auto - e.g.
auto p1 = pair('a', 1);
but if you want that specific type, you'd need to do something like
auto p1 = pair(cast(char)'a', 1u);
or
auto p1 = pair!(char, uint)('a', 1);
though if you're doing that, you don't even need the helper function and could just do
auto p1 = Pair!(char, uint)('a', 1);
The helper function does often help though, much as it's less helpful with those particular literals given the type that you want.
In any case, I would point out that unless you're doing something beyond what's typically for a pair or tuple type, there's no reason to declare a Pair type like what you have here. std.typecons.Tuple already takes care of that for you. So, Tuple!(char, uint) would declare basically the same type that you were trying to use and tuple can be used to construct on - e.g.
auto p1 = tuple('a', 1);
or
auto p1 = tuple(cast(char)'a', 1u);
or
auto p1 = Tuple!(char, uint)('a', 1);
- Jonathan M Davis
|