digitalmars.D - The bizarre world of typeof()
- Don (33/33) Oct 26 2009 I'm trying to make sense of the rules for 'typeof'. It's difficult
- Ary Borenszweig (8/22) Oct 26 2009 Not valid: typeof accepts an expression and "int*int" is not a valid
- Don (6/32) Oct 26 2009 Agreed.
- Lars T. Kyllingstad (18/59) Oct 26 2009 Here are a few more:
- Kagamin (2/5) Oct 26 2009 Failure is valid, compiler just can't show member function types correct...
- Lars T. Kyllingstad (14/21) Oct 26 2009 I'm not saying it should compile, I'm saying that the compiler should
- Kagamin (2/4) Oct 26 2009 why?
- Denis Koroskin (33/43) Oct 26 2009 This is somewhat invalid expression without a context:
- Kagamin (3/15) Oct 26 2009 Yes, the context is not *provided*, but is *required*.
- grauzone (7/34) Oct 26 2009 We need that to get the address of a function. It's just that the type
- Lars T. Kyllingstad (5/40) Oct 26 2009 Ok, thanks for explaining. Are there cases where it's useful to have a
- grauzone (5/49) Oct 26 2009 You could use it to dynamically build a delegate to that function. Or to...
- Don (11/81) Oct 26 2009 Dot has higher precedence than &, so it means &(Foo.bar), not (&Foo).bar...
- Lars T. Kyllingstad (10/69) Oct 26 2009 Ok, I see now. It's not wrong then, just ugly. :)
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
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
Ary Borenszweig wrote:Don wrote:Agreed.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.How does it get from Int to an instance of type Int? The first issue is typeof( T * U ). T and U are not variables, they are types.// 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
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
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
Kagamin wrote:Lars T. Kyllingstad Wrote: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// 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
Lars T. Kyllingstad Wrote:I'm saying that the compiler should give an error when it encounters the expression &Foo.barwhy?
Oct 26 2009
On Mon, 26 Oct 2009 16:38:42 +0300, Kagamin <spam here.lot> wrote:Lars T. Kyllingstad Wrote: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.I'm saying that the compiler should give an error when it encounters the expression &Foo.barwhy?Lars T. Kyllingstad Wrote: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!// 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
Denis Koroskin Wrote:Yes, the context is not *provided*, but is *required*. *function* doesn't require context.Lars T. Kyllingstad Wrote: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.// 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
Lars T. Kyllingstad wrote:Kagamin wrote: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 T. Kyllingstad Wrote: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.// 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.-Lars
Oct 26 2009
grauzone wrote:Lars T. Kyllingstad wrote:Ok, thanks for explaining. Are there cases where it's useful to have a pointer to a member function without its context?Kagamin wrote: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.Lars T. Kyllingstad Wrote: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.// 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.We also need typeof(&Foo.bar) to get the parameter and return types for the bar method.Good point. -Lars
Oct 26 2009
Lars T. Kyllingstad wrote:grauzone wrote: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.Lars T. Kyllingstad wrote:Ok, thanks for explaining. Are there cases where it's useful to have a pointer to a member function without its context?Kagamin wrote: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.Lars T. Kyllingstad Wrote: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.// 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.We also need typeof(&Foo.bar) to get the parameter and return types for the bar method.Good point. -Lars
Oct 26 2009
Lars T. Kyllingstad wrote:Don wrote: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'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
Don wrote:Lars T. Kyllingstad wrote: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 -LarsDon wrote: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'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.
Oct 26 2009