www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - The bizarre world of typeof()

reply Don <nospam nospam.com> writes:
I'm trying to make sense of the rules for 'typeof'. It's difficult 
because DMD's behaviour is so different to the spec. Here's four simple 
cases.

// This doesn't compile on D1.
//alias typeof(int*int) Alias1;

// This compiles in D1, but not in D2.
alias int Int;
alias typeof(Int*Int) Alias2;

// Yet this DOES compile on D2 !
typeof(T*U) foo(T, U)(T x, U y) { return x*y; }
alias typeof(foo(Int, Int)) Alias3;

// And this fails on both D1 and D2, with a dreadful error message.
//alias typeof(foo(int)) Alias4;

I can't see anything in the spec to say why ANY of these examples should 
compile. Yet, the existing template constraints features relies on the 
Alias3 case.

I can see two ways forward:
(1) enforce the existing spec. Make all uses of types as expressions 
into a bug. This will break a lot of existing code, including several in 
the DMD test suite!
You'd generally need to include a .init whenever using a type inside a 
typeof(). This would make some code a lot uglier.
I'm also not sure what happens with alias parameters. (If A is an alias 
to a type, then typeof(A*A) should be changed to typeof(A.init*A.init); 
but if it's an alias to a variable, it should remain as typeof(A*A)).

(2) Define that, inside a typeof() expression, any type T is translated 
into T.init. The syntax for typeof() would need to be changed, in order 
to allow the case 'alias1'.

Note, however, that in both cases there's no such thing as .init for 
tuples; it might need to be added.

Behaviour (2) is probably more convenient, behaviour (1) is easier to 
justify. But I think the existing behaviour of typeof() doesn't make 
much sense.
Oct 26 2009
next sibling parent reply Ary Borenszweig <ary esperanto.org.ar> writes:
Don wrote:
 I'm trying to make sense of the rules for 'typeof'. It's difficult 
 because DMD's behaviour is so different to the spec. Here's four simple 
 cases.
 
 // This doesn't compile on D1.
 //alias typeof(int*int) Alias1;

Not valid: typeof accepts an expression and "int*int" is not a valid expression.
 
 // This compiles in D1, but not in D2.
 alias int Int;
 alias typeof(Int*Int) Alias2;

Almost same as above: Int resolves to a type and "type*type" is not a valid expression.
 
 // Yet this DOES compile on D2 !
 typeof(T*U) foo(T, U)(T x, U y) { return x*y; }
 alias typeof(foo(Int, Int)) Alias3;

Of course, because this doesn't translate to "Int*Int", this translates to some variables x and y of type "Int" and "Int" respectively for which you can do "x*y".
Oct 26 2009
parent Don <nospam nospam.com> writes:
Ary Borenszweig wrote:
 Don wrote:
 I'm trying to make sense of the rules for 'typeof'. It's difficult 
 because DMD's behaviour is so different to the spec. Here's four 
 simple cases.

 // This doesn't compile on D1.
 //alias typeof(int*int) Alias1;

Not valid: typeof accepts an expression and "int*int" is not a valid expression.

Agreed.
 
 // This compiles in D1, but not in D2.
 alias int Int;
 alias typeof(Int*Int) Alias2;

Almost same as above: Int resolves to a type and "type*type" is not a valid expression.

Agreed.
 // Yet this DOES compile on D2 !
 typeof(T*U) foo(T, U)(T x, U y) { return x*y; }
 alias typeof(foo(Int, Int)) Alias3;

Of course, because this doesn't translate to "Int*Int", this translates to some variables x and y of type "Int" and "Int" respectively for which you can do "x*y".

The first issue is typeof( T * U ). T and U are not variables, they are types.
Oct 26 2009
prev sibling parent reply "Lars T. Kyllingstad" <public kyllingen.NOSPAMnet> writes:
Don wrote:
 I'm trying to make sense of the rules for 'typeof'. It's difficult 
 because DMD's behaviour is so different to the spec. Here's four simple 
 cases.
 
 // This doesn't compile on D1.
 //alias typeof(int*int) Alias1;
 
 // This compiles in D1, but not in D2.
 alias int Int;
 alias typeof(Int*Int) Alias2;
 
 // Yet this DOES compile on D2 !
 typeof(T*U) foo(T, U)(T x, U y) { return x*y; }
 alias typeof(foo(Int, Int)) Alias3;
 
 // And this fails on both D1 and D2, with a dreadful error message.
 //alias typeof(foo(int)) Alias4;
 
 I can't see anything in the spec to say why ANY of these examples should 
 compile. Yet, the existing template constraints features relies on the 
 Alias3 case.

Here are a few more: class Foo { real bar() { return 1.0; } } Foo foo = new Foo; // Passes, but should fail. static assert (is (typeof(foo.bar) == function)); // Passes, as expected. static assert (is (typeof(&foo.bar) == delegate)); // Passes, but should fail. This is similar to Don's examples. static assert (is (typeof(Foo.bar) == function)); // This one fails with the following hilarious message: // Error: static assert (is(real function() == function)) is false static assert (is (typeof(&Foo.bar) == function)); I have no idea why typeof(&Foo.bar) even works, but it does. &Foo is completely meaningless.
 I can see two ways forward:
 (1) enforce the existing spec. Make all uses of types as expressions 
 into a bug. This will break a lot of existing code, including several in 
 the DMD test suite!
 You'd generally need to include a .init whenever using a type inside a 
 typeof(). This would make some code a lot uglier.
 I'm also not sure what happens with alias parameters. (If A is an alias 
 to a type, then typeof(A*A) should be changed to typeof(A.init*A.init); 
 but if it's an alias to a variable, it should remain as typeof(A*A)).
 
 (2) Define that, inside a typeof() expression, any type T is translated 
 into T.init. The syntax for typeof() would need to be changed, in order 
 to allow the case 'alias1'.
 
 Note, however, that in both cases there's no such thing as .init for 
 tuples; it might need to be added.
 
 Behaviour (2) is probably more convenient, behaviour (1) is easier to 
 justify. But I think the existing behaviour of typeof() doesn't make 
 much sense.

I vote for (1). There should be as few "special cases" in the language as possible. -Lars
Oct 26 2009
next sibling parent reply Kagamin <spam here.lot> writes:
Lars T. Kyllingstad Wrote:

      // This one fails with the following hilarious message:
      // Error: static assert  (is(real function() == function)) is false
      static assert (is (typeof(&Foo.bar) == function));

Failure is valid, compiler just can't show member function types correctly.
Oct 26 2009
parent reply "Lars T. Kyllingstad" <public kyllingen.NOSPAMnet> writes:
Kagamin wrote:
 Lars T. Kyllingstad Wrote:
 
      // This one fails with the following hilarious message:
      // Error: static assert  (is(real function() == function)) is false
      static assert (is (typeof(&Foo.bar) == function));

Failure is valid, compiler just can't show member function types correctly.

I'm not saying it should compile, I'm saying that the compiler should give an error when it encounters the expression &Foo.bar, and not just because of the failed assertion. It's bad enough that it accepts Foo.bar (this is what Don was talking about), but allowing one to take the address as well is just nonsense -- even when it's in an is(typeof()) expression. In fact, &Foo.bar actually returns an address. The following compiles: class Foo { real bar() { return 1.0; } } auto f = &Foo.bar; auto x = f(); Of course, when run, it segfaults on the last line. I wonder where f actually points to. -Lars
Oct 26 2009
next sibling parent reply Kagamin <spam here.lot> writes:
Lars T. Kyllingstad Wrote:

 I'm saying that the compiler should 
 give an error when it encounters the expression &Foo.bar

Oct 26 2009
parent Kagamin <spam here.lot> writes:
Denis Koroskin Wrote:

 Lars T. Kyllingstad Wrote:

      // This one fails with the following hilarious message:
      // Error: static assert  (is(real function() == function)) is false
      static assert (is (typeof(&Foo.bar) == function));

Failure is valid, compiler just can't show member function types correctly.

That's a correct behavior. &Foo.bar returns a *function*, not a delegate (because of a lack of context), and no type information is associated with it.

Yes, the context is not *provided*, but is *required*. *function* doesn't require context.
Oct 26 2009
prev sibling parent reply grauzone <none example.net> writes:
Lars T. Kyllingstad wrote:
 Kagamin wrote:
 Lars T. Kyllingstad Wrote:

      // This one fails with the following hilarious message:
      // Error: static assert  (is(real function() == function)) is false
      static assert (is (typeof(&Foo.bar) == function));

Failure is valid, compiler just can't show member function types correctly.

I'm not saying it should compile, I'm saying that the compiler should give an error when it encounters the expression &Foo.bar, and not just because of the failed assertion. It's bad enough that it accepts Foo.bar (this is what Don was talking about), but allowing one to take the address as well is just nonsense -- even when it's in an is(typeof()) expression. In fact, &Foo.bar actually returns an address. The following compiles: class Foo { real bar() { return 1.0; } } auto f = &Foo.bar; auto x = f(); Of course, when run, it segfaults on the last line. I wonder where f actually points to.

We need that to get the address of a function. It's just that the type of the returned object is a bit bogus: it's not really a function; it's a method pointer casted to a function pointer. It simply has the wrong calling convention. We also need typeof(&Foo.bar) to get the parameter and return types for the bar method.
 -Lars

Oct 26 2009
parent reply "Lars T. Kyllingstad" <public kyllingen.NOSPAMnet> writes:
grauzone wrote:
 Lars T. Kyllingstad wrote:
 Kagamin wrote:
 Lars T. Kyllingstad Wrote:

      // This one fails with the following hilarious message:
      // Error: static assert  (is(real function() == function)) is 
 false
      static assert (is (typeof(&Foo.bar) == function));

Failure is valid, compiler just can't show member function types correctly.

I'm not saying it should compile, I'm saying that the compiler should give an error when it encounters the expression &Foo.bar, and not just because of the failed assertion. It's bad enough that it accepts Foo.bar (this is what Don was talking about), but allowing one to take the address as well is just nonsense -- even when it's in an is(typeof()) expression. In fact, &Foo.bar actually returns an address. The following compiles: class Foo { real bar() { return 1.0; } } auto f = &Foo.bar; auto x = f(); Of course, when run, it segfaults on the last line. I wonder where f actually points to.

We need that to get the address of a function. It's just that the type of the returned object is a bit bogus: it's not really a function; it's a method pointer casted to a function pointer. It simply has the wrong calling convention.

Ok, thanks for explaining. Are there cases where it's useful to have a pointer to a member function without its context?
 We also need typeof(&Foo.bar) to get the parameter and return types for 
 the bar method.

Good point. -Lars
Oct 26 2009
parent grauzone <none example.net> writes:
Lars T. Kyllingstad wrote:
 grauzone wrote:
 Lars T. Kyllingstad wrote:
 Kagamin wrote:
 Lars T. Kyllingstad Wrote:

      // This one fails with the following hilarious message:
      // Error: static assert  (is(real function() == function)) is 
 false
      static assert (is (typeof(&Foo.bar) == function));

Failure is valid, compiler just can't show member function types correctly.

I'm not saying it should compile, I'm saying that the compiler should give an error when it encounters the expression &Foo.bar, and not just because of the failed assertion. It's bad enough that it accepts Foo.bar (this is what Don was talking about), but allowing one to take the address as well is just nonsense -- even when it's in an is(typeof()) expression. In fact, &Foo.bar actually returns an address. The following compiles: class Foo { real bar() { return 1.0; } } auto f = &Foo.bar; auto x = f(); Of course, when run, it segfaults on the last line. I wonder where f actually points to.

We need that to get the address of a function. It's just that the type of the returned object is a bit bogus: it's not really a function; it's a method pointer casted to a function pointer. It simply has the wrong calling convention.

Ok, thanks for explaining. Are there cases where it's useful to have a pointer to a member function without its context?

You could use it to dynamically build a delegate to that function. Or to allow serialization of delegates. Maybe there are other uses as well. Anyway, you really shouldn't have to instantiate a class just to get method addresses.
 
 We also need typeof(&Foo.bar) to get the parameter and return types 
 for the bar method.

Good point. -Lars

Oct 26 2009
prev sibling next sibling parent "Denis Koroskin" <2korden gmail.com> writes:
On Mon, 26 Oct 2009 16:38:42 +0300, Kagamin <spam here.lot> wrote:

 Lars T. Kyllingstad Wrote:

 I'm saying that the compiler should
 give an error when it encounters the expression &Foo.bar


This is somewhat invalid expression without a context: class Foo { int bar() { return 42; } } auto dg1 = &Foo.bar; // fine? int x = dg1(); // fine, too? But sometimes context is implicit: class Derived : Foo { auto get() { return &Foo.bar; // context is this and is implicit } } auto dg2 = (new Derived()).get(); int y = dg2(); // okay Also sometimes you need a function and don't care about context: void delegate(int x) dg; dg.funcptr = &Foo.bar; dg.ptr = new Foo(); int z = dg(); // also fine So &Foo.bar should stay.
 Lars T. Kyllingstad Wrote:

      // This one fails with the following hilarious message:
      // Error: static assert  (is(real function() == function)) is false
      static assert (is (typeof(&Foo.bar) == function));

Failure is valid, compiler just can't show member function types correctly.

That's a correct behavior. &Foo.bar returns a *function*, not a delegate (because of a lack of context), and no type information is associated with it. Well, it's could return a delegate (with a null context), too, but then reassigning delegate.funcptr would be less obvious: dg.funcptr = (&Foo.bar).funcptr; And it won't save you from invoking a delegate with a missing context: int delegate() dg = &Foo.bar; dg(); // oops!
Oct 26 2009
prev sibling parent reply Don <nospam nospam.com> writes:
Lars T. Kyllingstad wrote:
 Don wrote:
 I'm trying to make sense of the rules for 'typeof'. It's difficult 
 because DMD's behaviour is so different to the spec. Here's four 
 simple cases.

 // This doesn't compile on D1.
 //alias typeof(int*int) Alias1;

 // This compiles in D1, but not in D2.
 alias int Int;
 alias typeof(Int*Int) Alias2;

 // Yet this DOES compile on D2 !
 typeof(T*U) foo(T, U)(T x, U y) { return x*y; }
 alias typeof(foo(Int, Int)) Alias3;

 // And this fails on both D1 and D2, with a dreadful error message.
 //alias typeof(foo(int)) Alias4;

 I can't see anything in the spec to say why ANY of these examples 
 should compile. Yet, the existing template constraints features relies 
 on the Alias3 case.

Here are a few more: class Foo { real bar() { return 1.0; } } Foo foo = new Foo; // Passes, but should fail. static assert (is (typeof(foo.bar) == function)); // Passes, as expected. static assert (is (typeof(&foo.bar) == delegate)); // Passes, but should fail. This is similar to Don's examples. static assert (is (typeof(Foo.bar) == function)); // This one fails with the following hilarious message: // Error: static assert (is(real function() == function)) is false static assert (is (typeof(&Foo.bar) == function)); I have no idea why typeof(&Foo.bar) even works, but it does. &Foo is completely meaningless.

Dot has higher precedence than &, so it means &(Foo.bar), not (&Foo).bar. The static assert fails because "real function()" is a *function pointer*, but is(xxx == function) tests to see if xxx is a *function*, not a *function pointer*. So this passes: void function () goo; static assert( is (typeof(*goo) == function)); It's pretty awful that that in "is(real function() == function)", the keyword 'function' has two contradictory meanings in the same expression. And the spec never says what "function type" is.
 I can see two ways forward:
 (1) enforce the existing spec. Make all uses of types as expressions 
 into a bug. This will break a lot of existing code, including several 
 in the DMD test suite!
 You'd generally need to include a .init whenever using a type inside a 
 typeof(). This would make some code a lot uglier.
 I'm also not sure what happens with alias parameters. (If A is an 
 alias to a type, then typeof(A*A) should be changed to 
 typeof(A.init*A.init); but if it's an alias to a variable, it should 
 remain as typeof(A*A)).

 (2) Define that, inside a typeof() expression, any type T is 
 translated into T.init. The syntax for typeof() would need to be 
 changed, in order to allow the case 'alias1'.

 Note, however, that in both cases there's no such thing as .init for 
 tuples; it might need to be added.

 Behaviour (2) is probably more convenient, behaviour (1) is easier to 
 justify. But I think the existing behaviour of typeof() doesn't make 
 much sense.

I vote for (1). There should be as few "special cases" in the language as possible. -Lars

Oct 26 2009
parent "Lars T. Kyllingstad" <public kyllingen.NOSPAMnet> writes:
Don wrote:
 Lars T. Kyllingstad wrote:
 Don wrote:
 I'm trying to make sense of the rules for 'typeof'. It's difficult 
 because DMD's behaviour is so different to the spec. Here's four 
 simple cases.

 // This doesn't compile on D1.
 //alias typeof(int*int) Alias1;

 // This compiles in D1, but not in D2.
 alias int Int;
 alias typeof(Int*Int) Alias2;

 // Yet this DOES compile on D2 !
 typeof(T*U) foo(T, U)(T x, U y) { return x*y; }
 alias typeof(foo(Int, Int)) Alias3;

 // And this fails on both D1 and D2, with a dreadful error message.
 //alias typeof(foo(int)) Alias4;

 I can't see anything in the spec to say why ANY of these examples 
 should compile. Yet, the existing template constraints features 
 relies on the Alias3 case.

Here are a few more: class Foo { real bar() { return 1.0; } } Foo foo = new Foo; // Passes, but should fail. static assert (is (typeof(foo.bar) == function)); // Passes, as expected. static assert (is (typeof(&foo.bar) == delegate)); // Passes, but should fail. This is similar to Don's examples. static assert (is (typeof(Foo.bar) == function)); // This one fails with the following hilarious message: // Error: static assert (is(real function() == function)) is false static assert (is (typeof(&Foo.bar) == function)); I have no idea why typeof(&Foo.bar) even works, but it does. &Foo is completely meaningless.

Dot has higher precedence than &, so it means &(Foo.bar), not (&Foo).bar. The static assert fails because "real function()" is a *function pointer*, but is(xxx == function) tests to see if xxx is a *function*, not a *function pointer*. So this passes: void function () goo; static assert( is (typeof(*goo) == function)); It's pretty awful that that in "is(real function() == function)", the keyword 'function' has two contradictory meanings in the same expression. And the spec never says what "function type" is.

Ok, I see now. It's not wrong then, just ugly. :) static assert (is (int delegate() == delegate)); // passes static assert (is (int function() == function)); // fails What about my first example then, is that the intended behaviour as well? With the current property syntax, I'd expect this to work, but it doesn't: static assert (is (typeof(foo.bar) == typeof(foo.bar()))); Error: static assert (is(real() == real)) is false -Lars
Oct 26 2009