August 25, 2010
Page 373 adds another operator overload for use with integral numbers (CheckedInt op Int). But it conflicts with the previos template (CheckedInt op CheckedInt):

module binary_ops;

import std.stdio : writeln;
import std.traits;
import std.exception;

unittest
{
    auto foo = CheckedInt!(int)(5);

    foo + 4;
}

void main()
{
}

struct CheckedInt(N) if (isIntegral!N)
{
    private N value;
    public int x;

    this(N value)
    {
        this.value = value;
    }

    // Operation with raw numbers
    CheckedInt opBinary(string op)(N rhs) if (isIntegral!N)
    {
        return opBinary!op(CheckedInt(rhs));
    }

    // addition
    CheckedInt opBinary(string op)(CheckedInt rhs) if (op == "+")
    {
        auto result = value + rhs.value;

        enforce(rhs.value >= 0 ? result >= value : result < value);
        return CheckedInt(result);
    }
}

I get back:

binary_ops.d(34): Error: template instance opBinary!(op) matches more than one template declaration,
binary_ops.d(32):opBinary(string op) if (isIntegral!(N)) and
binary_ops.d(38):opBinary(string op) if (op == "+")

One is taking an integral by using a constraint, the other specifically a CheckedInt type. Any clues how they both match?

If I remove the first operator overload template then I can't compile, so where's the ambiguity?

August 25, 2010
On Wed, 25 Aug 2010 16:54:19 -0400, Andrej Mitrovic <andrej.mitrovich@name.com> wrote:

> Page 373 adds another operator overload for use with integral numbers (CheckedInt op Int). But it conflicts with the previos template (CheckedInt op CheckedInt):
>
> module binary_ops;
>
> import std.stdio : writeln;
> import std.traits;
> import std.exception;
>
> unittest
> {
>     auto foo = CheckedInt!(int)(5);
>
>     foo + 4;
> }
>
> void main()
> {
> }
> struct CheckedInt(N) if (isIntegral!N)
> {
>     private N value;
>     public int x;
>    this(N value)
>     {
>         this.value = value;
>     }
>    // Operation with raw numbers
>     CheckedInt opBinary(string op)(N rhs) if (isIntegral!N)
>     {
>         return opBinary!op(CheckedInt(rhs));
>     }
>    // addition
>     CheckedInt opBinary(string op)(CheckedInt rhs) if (op == "+")
>     {
>         auto result = value + rhs.value;
>        enforce(rhs.value >= 0 ? result >= value : result < value);
>         return CheckedInt(result);
>     }
> }
>
> I get back:
>
> binary_ops.d(34): Error: template instance opBinary!(op) matches more than one template declaration,
> binary_ops.d(32):opBinary(string op) if (isIntegral!(N)) and
> binary_ops.d(38):opBinary(string op) if (op == "+")
>
> One is taking an integral by using a constraint, the other specifically a CheckedInt type. Any clues how they both match?
>
> If I remove the first operator overload template then I can't compile, so where's the ambiguity?

This is an issue with template overloading.

To the compiler, the template instantiations are identical, because opBinary(string op) only has one template parameter.  The arguments to the function are only looked at afterwards for overloading.  So the compiler wants to make a decision first of which *template* to instantiate, the first or the second, then looks at function overloading.

I believe there's a bug on this in bugzilla already.  It makes things more difficult for operator overloading.

Also that first function signature is wrong.  N is not a template parameter to the *function* and isIntegral!N is already checked on the struct, so there is no need to add that constraint again.  So with the constraint removed, we can work around the problem by adding a superfluous template argument

>    // Operation with raw numbers
>     CheckedInt opBinary(string op, T)(T rhs) if (is(T == N))
>     {
>         return opBinary!op(CheckedInt(rhs));
>     }
>    // addition
>     CheckedInt opBinary(string op, T)(T rhs) if (op == "+" && is(T == CheckedInt))
>     {
>         auto result = value + rhs.value;
>        enforce(rhs.value >= 0 ? result >= value : result < value);
>         return CheckedInt(result);
>     }

-Steve