Thread overview
Asking types about their traits (D 1.0)
Feb 11, 2008
Burton Radons
Feb 11, 2008
Burton Radons
Feb 11, 2008
Burton Radons
Feb 11, 2008
Burton Radons
Feb 13, 2008
downs
February 11, 2008
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?
February 11, 2008
"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!


February 11, 2008
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.
February 11, 2008
"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.


February 11, 2008
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.
February 11, 2008
"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.


February 11, 2008
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.
February 13, 2008
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