www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Asking types about their traits (D 1.0)

reply Burton Radons <burton-radons shaw.ca> writes:
I'm writing some templated code that I want to automatically behave properly
when applied to any other type which behaves according to an interface. There
are two sides to this. The first is when I want to test for it behaving like an
integer. In this case, if I already have an algorithm written for an integer,
then it should work for it automatically. In the other case, I want to be able
to ask it whether it implements a method, such as "pow". Then I'd call that
method instead of using my own code, which may be suboptimal.

Both of these are implementable by having an alias or typedef in the type, such
as with:

	/// A dummy type that is used to indicate the presence of a trait.
	struct Trait
	{
	}
	
	struct Bignum 
	{
		/// Traits supported by Bignum.
		alias Trait IsInt, HasPow;

		// We're an integer type so our pow function works just fine, but we can
implement it faster and control our temporaries.
		Bignum pow (Bignum value) { ... }
	}
	
	/// Evaluates to true if the type is a builtin integral type (byte, ubyte,
short, ushort, int, uint, long, ulong) or if it has the IsInt trait.
	template is_int (T)
	{
		const bool is_int = is_type! (T, byte, ubyte, short, ushort, int, uint, long,
ulong) || is (T.IsInt == Trait);
	}

	/// Evaluate to true if T is any of the given types.
	template is_type (T, Y...)
	{
		static if (Y.length > 1)
			const bool is_type = is (T == Y [0]) || is_type! (T, Y [1 .. $]);
		else static if (Y.length == 1)
			const bool is_type = is (T == Y [0]);
		else
			const bool is_type = false;
	}

	is_int! (int) == true;
	is_int! (float) == false;
	is_int! (Bignum) == true;

But I'd like it if the presence of a method didn't require a superfluous alias,
but could be detected automatically with something like "is (T.pow : T delegate
(T))". Any ideas?

Maybe I shouldn't be fighting this - the presence of a method even with the
correct signature doesn't necessarily imply that it supports the functionality
we're requesting. On the other hand, the likelihood of incorrect code being
generated seems extremely minute versus the expense of the alias. What do you
think?
Feb 10 2008
next sibling parent reply "Jarrett Billingsley" <kb3ctd2 yahoo.com> writes:
"Burton Radons" <burton-radons shaw.ca> wrote in message 
news:foo388$qsh$1 digitalmars.com...

 But I'd like it if the presence of a method didn't require a superfluous 
 alias, but could be detected automatically with something like "is (T.pow 
 : T delegate (T))". Any ideas?

 Maybe I shouldn't be fighting this - the presence of a method even with 
 the correct signature doesn't necessarily imply that it supports the 
 functionality we're requesting. On the other hand, the likelihood of 
 incorrect code being generated seems extremely minute versus the expense 
 of the alias. What do you think?

struct A { } struct B { B pow(B other) { return B(); } } template HasPow(T) { const HasPow = is(typeof(&T.pow) == T function(T)); } pragma(msg, HasPow!(A) ? "true" : "false"); // prints false pragma(msg, HasPow!(B) ? "true" : "false"); // prints true You were pretty close!
Feb 10 2008
parent reply Burton Radons <burton-radons shaw.ca> writes:
Jarrett Billingsley Wrote:

 "Burton Radons" <burton-radons shaw.ca> wrote in message 
 news:foo388$qsh$1 digitalmars.com...
 
 But I'd like it if the presence of a method didn't require a superfluous 
 alias, but could be detected automatically with something like "is (T.pow 
 : T delegate (T))". Any ideas?

 Maybe I shouldn't be fighting this - the presence of a method even with 
 the correct signature doesn't necessarily imply that it supports the 
 functionality we're requesting. On the other hand, the likelihood of 
 incorrect code being generated seems extremely minute versus the expense 
 of the alias. What do you think?

struct A { } struct B { B pow(B other) { return B(); } } template HasPow(T) { const HasPow = is(typeof(&T.pow) == T function(T)); } pragma(msg, HasPow!(A) ? "true" : "false"); // prints false pragma(msg, HasPow!(B) ? "true" : "false"); // prints true You were pretty close!

Ah, I thought "nah, it'll just tell me to go to hell since it's invalid code" so I didn't try that. That seems the incorrect type though; it should be "T function (T, inout T)", which works correctly. The only problem with that is that variadic functions would be "T function (..., inout T)", which actually makes sense to the ABI (that implementations must conform to), so why not allow it, at least for types? It would definitely be an easier solution than any C++-style member-function nonsense.
Feb 10 2008
parent reply "Jarrett Billingsley" <kb3ctd2 yahoo.com> writes:
"Burton Radons" <burton-radons shaw.ca> wrote in message 
news:fooeas$1lkd$1 digitalmars.com...
 That seems the incorrect type though; it should be "T function (T, inout 
 T)", which works correctly. The only problem with that is that variadic 
 functions would be "T function (..., inout T)", which actually makes sense 
 to the ABI (that implementations must conform to), so why not allow it, at 
 least for types? It would definitely be an easier solution than any 
 C++-style member-function nonsense.

I guess what you're getting at is that you would expect the parameter list to include the "this" pointer? The thing is that a delegate does not use the same calling convention as a normal function, so there's no way to implicitly convert between the two without thunking. It's why the 'this' pointer is not included in the parameter list of the address of a member function. That function pointer is just half of what you need to call it, the other half of course being the object to call it on. Another point to consider is that GDC doesn't use the same calling convention as DMD, so where the 'this' pointer is passed might be / probably is different.
Feb 10 2008
parent reply Burton Radons <burton-radons shaw.ca> writes:
Jarrett Billingsley Wrote:

 "Burton Radons" <burton-radons shaw.ca> wrote in message 
 news:fooeas$1lkd$1 digitalmars.com...
 That seems the incorrect type though; it should be "T function (T, inout 
 T)", which works correctly. The only problem with that is that variadic 
 functions would be "T function (..., inout T)", which actually makes sense 
 to the ABI (that implementations must conform to), so why not allow it, at 
 least for types? It would definitely be an easier solution than any 
 C++-style member-function nonsense.

I guess what you're getting at is that you would expect the parameter list to include the "this" pointer? The thing is that a delegate does not use the same calling convention as a normal function, so there's no way to implicitly convert between the two without thunking. It's why the 'this' pointer is not included in the parameter list of the address of a member function. That function pointer is just half of what you need to call it, the other half of course being the object to call it on.

Dude, no thunk needed. What I said works - EAX is used for the last argument, and it's used for the "this" argument when calling functions, so reconfiguring as I said works for structs and it works for classes: import std.stdio; struct Struct { int field; void func (int value) { writef ("Hello from Struct with %s, my field is %s!\n", value, field); } } class Class { int field; void func (int value) { writef ("Hello from Class with %s, my field is %s!\n", value, field); } } void main () { Struct s; Class c = new Class; s.field = 42; c.field = 67; // Don't call this, it crashes the runtime even though it appears to be valid D code! // (&Struct.func) (16); // So does this! Why are you compiling invalid code, DMD? // (&Class.func) (12); // But this is super! auto fs = cast (void function (int, inout Struct)) &Struct.func; fs (16, s); // Prints "Hello from Struct with 16, my field is 42!" // So is this! auto cs = cast (void function (int, Class)) &Class.func; cs (12, c); // Prints "Hello from Class with 12, my field is 67!" } Although I'll say that making EAX the LAST argument is bizarre.
 Another point to consider is that GDC doesn't use the same calling 
 convention as DMD, so where the 'this' pointer is passed might be / probably 
 is different. 

Ah! I read "A D implementation that conforms to the D ABI (Application Binary Interface) will be able to generate libraries, DLL's, etc., that can interoperate with D binaries built by other implementations." as "A D implementation conforms to the D ABI". Tricksy standard! I haven't been in GCC's bowels for five years, but as I remember it frontends have control over calling conventions. Anyway, regardless of anything, DMD should never compile invalid code like that.
Feb 10 2008
parent reply "Jarrett Billingsley" <kb3ctd2 yahoo.com> writes:
"Burton Radons" <burton-radons shaw.ca> wrote in message 
news:foot25$2mku$1 digitalmars.com...
 Dude, no thunk needed. What I said works - EAX is used for the last 
 argument, and it's used for the "this" argument when calling functions, so 
 reconfiguring as I said works for structs and it works for classes:

 ....

 Although I'll say that making EAX the LAST argument is bizarre.

All I'll say is that there are architectures other than x86-32.
Feb 11 2008
parent Burton Radons <burton-radons shaw.ca> writes:
Jarrett Billingsley Wrote:

 "Burton Radons" <burton-radons shaw.ca> wrote in message 
 news:foot25$2mku$1 digitalmars.com...
 Dude, no thunk needed. What I said works - EAX is used for the last 
 argument, and it's used for the "this" argument when calling functions, so 
 reconfiguring as I said works for structs and it works for classes:

 ....

 Although I'll say that making EAX the LAST argument is bizarre.

All I'll say is that there are architectures other than x86-32.

All that requires is the declaration that the last parameter to a function behaves the same as a "this" pointer in a delegate, however that is interpreted in the host environment. Either way keep in mind that there must be a solution to this; DMD can't be producing invalid code like that.
Feb 11 2008
prev sibling parent downs <default_357-line yahoo.de> writes:
Burton Radons wrote:
 I'm writing some templated code that I want to automatically behave properly
when applied to any other type which behaves according to an interface. There
are two sides to this. The first is when I want to test for it behaving like an
integer. In this case, if I already have an algorithm written for an integer,
then it should work for it automatically. In the other case, I want to be able
to ask it whether it implements a method, such as "pow". Then I'd call that
method instead of using my own code, which may be suboptimal.
 

It sounds like you want something like Duck Typing :) Example. template Init(T) { T Init; } template behavesLikeInt(T) { // tests, basically, if T * int -> :int const bool behavesLikeInt = is(typeof(Init!(T) * 2) : int); } template hasPowWithInt(T) { const bool hasPowWithInt = is(typeof(Init!(T).pow(2))); } Does that help? --downs
Feb 13 2008