Struct template cannot deduce function from argument types

Jonathan M Davis newsgroup.d at jmdavisprog.com
Wed Jun 27 17:07:52 UTC 2018


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



More information about the Digitalmars-d-learn mailing list