December 10, 2011
On Saturday, 10 December 2011 at 12:15:14 UTC, David Nadlinger wrote:
> So, could you please at least elaborate why you think the current behavior is correct, and what advantages it has to outweigh the massive confusion it causes?

This. I don't see how it makes any sense from a design perspective.
December 10, 2011
On 12/10/11 12:11 PM, Walter Bright wrote:
> That's because
>
> Matrix!(int, cast(int)1)
>
> is considered a different template instantiation (and hence a different
> type) from
>
> Matrix!(int, cast(uint)1)
>
> Instantiation types are based on the arguments' types, not the
> parameters' types.

Which is, in my not-so-humble opinion, just plain broken. Template value parameters are, well, values, and there shouldn't ever be a difference between whether a value of an integral type represents 1 or 1u (if it accepts both).

This is a long-standing bug, and makes unsigned template value parameters and, to a lesser extent, integral template value parameters in general virtually unusable, because you hit hard to debug issues with confusing error messages all over the place.

It gets even worse once CTFE comes into play (or compile-time values in general, as opposed to directly passing number literals) for two reasons: First, the error messages become totally useless, i.e. in most cases something similar to »Foo!(n) can't be implicitly converted to Foo!(n)«. Second, even if you litter your code with explicit casts all over the place, it is not always possible to get it to behave like intended because of what I presume are issues with constant folding.

I know this first hand, because I worked on a units-of-measurement library which relies on units being uniquely represented as types, because the unit type becomes part of the type of quantities as a template parameter (I presented it at the NG a while back, by the way: http://www.digitalmars.com/d/archives/digitalmars/D/RFC_Units_of_measurement_for_D_Phobos_134590.html). For derived units (e.g. kg m/s^2), you need to »normalize« the exponents using compile time rational arithmetic, which was really hard to get to work because of the issue discussed here (I don't even remember all the details). But even with my own unit tests passing, I now once in a while get a message saying something like: »Hey, I checked out your units library prototype, and while it works nice for most cases, I encountered this situation where … [some situation in which 1 != 1 for DMD]«. I tried hard to work around this, but I couldn't find a reliable solution.

Also, I'd like to note that I am not alone with this opinion; Don, Kenji and Jonathan, among others, agree that the current behavior is confusing. A few reports of this issue (and related ones):

http://d.puremagic.com/issues/show_bug.cgi?id=3467
http://d.puremagic.com/issues/show_bug.cgi?id=2257
http://d.puremagic.com/issues/show_bug.cgi?id=2550

Kenji even came up with a fix in form of a pull request, which you turned down without further explanation:

https://github.com/D-Programming-Language/dmd/pull/449

So, could you please at least elaborate on why you think the current behavior is correct, and how its advantages outweigh the massive confusion it causes?

Thanks,
David
December 10, 2011
> This is a long-standing bug, and makes unsigned template value parameters and, to a lesser extent, integral template value parameters in general virtually unusable, because you hit hard to debug issues with confusing error messages all over the place.

I absolutely agree.
December 10, 2011
On 12/10/2011 3:33 AM, Walter Bright wrote:
> On 12/10/2011 3:14 AM, Mehrdad wrote:
>> ... and another... (yes, this one _IS_ a const issue)
>>
>> struct S { int opApply(scope int delegate(ref inout(int)) dg) inout { return 0; } }
>> void main()
>> {
>> foreach (i; S()) { }
>> }
>>
>> Error: inout on parameter means inout must be on return type as well (if from D1
>> code, replace with 'ref')
>
> Right. inout has no point if it also does not appear on the return type.
>
> The purpose of inout is to transfer the 'constness' of the argument to the return type. If inout isn't on the return type somewhere, there's likely a design mistake in your code. It's like having:
>
> {
>     a + b;
> }
>
> Which is an error in D because such is pointless.
Sure, it's always a design mistake. :-)

So how are you supposed to implement opApply on a container (or e.g. here, a matrix)? Copy/paste the code for const- and non-const versions?
December 10, 2011
On 12/10/2011 3:27 AM, Walter Bright wrote:
>> (I don't understand
>> why Matrix!(int, cast(int)1) is considered a different instantiation, when it
>> can't even be instantiated...)
>
> Yes, it can be instantiated.

Uh, what?

How can it be instantiated? There's no such template in my code...
December 10, 2011
On 12/10/11 5:10 AM, Mehrdad wrote:
> On 12/10/2011 3:08 AM, bearophile wrote:
>> Timon Gehr:
>>
>>> No, popFront is mutating and const(int[]) cannot be mutated.
>> So, is it impossible by design to iterate immutable collections?
>>
>> Bye,
>> bearophile
>
> I believe the answer is yes (although Timon would probably know better).
>
> That's one reason I believe const is broken...

That is incorrect (it's easy to design a range iterating over an immutable collection, and in particular const(T)[] iterates a const T[] no problem), but taking the time to vent about the annoyances you've encountered is highly appreciated.

I suspect many people try D just like you do, with a few cool ideas (no conservative "hello world" programs), see they don't work for obscure reasons, go like "meh", and give up. So having such an experience _documented_ is of great value.

I'm every bit as annoyed as you are about simple things not working with qualifiers, and I have a few ideas on how to fix it. I should add that I very strongly believe a problem with a workaround does not implicitly cease being a problem.


Andrei
December 10, 2011
On 12/10/11 5:11 AM, Walter Bright wrote:
> On 12/10/2011 3:04 AM, Mehrdad wrote:
>> On 12/10/2011 3:01 AM, Walter Bright wrote:
>>> On 12/10/2011 2:34 AM, Mehrdad wrote:
>>>> ... and another...
>>>>
>>>> struct Matrix(T, size_t N = 1)
>>>> { public auto mul(Matrix!(T, N) other) const { return this; } }
>>>
>>>
>>> In general, to refer to the current template being expanded from
>>> inside of it,
>>> just give its name, as in:
>>>
>>> Matrix!(T,N) => Matrix
>> That's not the issue.
>>
>> If you say 'int' instead of size_t, it works fine.
>
> That's because
>
> Matrix!(int, cast(int)1)
>
> is considered a different template instantiation (and hence a different
> type) from
>
> Matrix!(int, cast(uint)1)
>
> Instantiation types are based on the arguments' types, not the
> parameters' types.
>
> If you use my suggestion for the shorthand notation, your code will work
> as you expect.

Please let's not deflect the issue. If there's a patentable way to annoy an annoyed user even more, this must be it. A bug is a bug is a bug. Matrix can _only_ be instantiated with size_t, it says so in the definition:

struct Matrix(T, size_t N = 1) { ... }

THAT IS NOT:

struct Matrix(T, alias N = cast(size_t) 1) { ... }

as the current compiler seems to wrongly implement it. The fact that one can actually instantiate it with int or whatnot should first convert the int to size_t, and then instantiate the matrix with size_t.

http://d.puremagic.com/issues/show_bug.cgi?id=7090

Thanks Mehrdad.


Andrei
December 10, 2011
On 12/10/11 5:27 AM, Walter Bright wrote:
> On 12/10/2011 3:17 AM, Mehrdad wrote:
>> Yup, I started using it as soon as Timon mentioned it (thanks for the
>> suggestion!).
>> But I was referring to the bug, not to the workaround. :)
>
> It isn't a bug, it is designed to work that way.

The design is wrong.

> The type of the
> instantiation is based on the argument types, not the parameter types.

This is wrong. The type of parameter is size_t, no two ways about it. It is NOT alias.

> (Note that "2" is the argument and "N" is the parameter.)

Not that N has type size_t.

> Hence the message:
>
> Error: cannot implicitly convert expression (m) of type Matrix!(int,2)
> to Matrix!(int,N)

The message reflects a mistake.

>> (I don't understand
>> why Matrix!(int, cast(int)1) is considered a different instantiation,
>> when it
>> can't even be instantiated...)
>
> Yes, it can be instantiated. But it cannot be implicitly converted to
> type Matrix!(int, cast(uint)1) because they are different types.

They shouldn't.


Andrei
December 10, 2011
On 12/10/2011 5:04 AM, David Nadlinger wrote:
> So, could you please at least elaborate on why you think the current behavior is
> correct, and how its advantages outweigh the massive confusion it causes?

The original idea was that you could add specializations without breaking existing binaries. However, experience seems to indicate that this isn't worth it.
December 10, 2011
Oh, nice, here's an ICE for y'all :)


struct Matrix(T)
{
    @property T[] data() { return null; }

    int opApply(scope int delegate(ref size_t[], ref T) dg) { return 0; }
    int opApply(scope int delegate(ref const(size_t[]), ref const(T)) dg) const { return 0; }

    Matrix!(typeof(mixin("data[0] " ~ op ~ " data[0]"))) opBinary(string op)(Matrix other)
    {
        auto result = typeof(return)();
        foreach (i, ref val; this)
        { mixin("result[i] = val " ~ op ~ " other[i];"); }
        return result;
    }
}

void main() { auto m = Matrix!size_t(); m = m * m; }