Thread overview
Introduction to traits (and __traits)
Aug 30, 2013
Benjamin Thaut
Aug 30, 2013
Ali Çehreli
Aug 30, 2013
bearophile
Aug 30, 2013
H. S. Teoh
August 30, 2013
Hello all,

I find myself wanting to write for the first time one of these isSomething(T) or hasSomething(T) templates that perform compile-time checks, so I was hoping people could give me some good general advice on traits.

The goal here is to be able to confirm (i) type T has certain members and (ii) they have certain properties.  (This is for my graph library, Dgraph.)

So, to start off with, I thought I'd try starting with,

    template isGraph(G)
    {
        isGraph = __traits(hasMember, G, "directed") && isBoolean!(typeof(G.directed));
    }

... which I'd assumed would cover the case where G does not have a member "directed".  But in fact if I pass it a struct that does not have the entity .directed defined therein, it will return an error:  "no property 'directed' for type ..."

So, can someone give me a good idea of how to go about writing such a compile-time template that checks (i) for the existence of certain methods/functions/members and (ii) confirms their characteristics, such as their return values or arguments?

Thanks & best wishes,

    -- Joe
August 30, 2013
Am 30.08.2013 21:36, schrieb Joseph Rushton Wakeling:
> Hello all,
>
> I find myself wanting to write for the first time one of these
> isSomething(T) or hasSomething(T) templates that perform compile-time
> checks, so I was hoping people could give me some good general advice on
> traits.
>
> The goal here is to be able to confirm (i) type T has certain members
> and (ii) they have certain properties.  (This is for my graph library,
> Dgraph.)
>
> So, to start off with, I thought I'd try starting with,
>
>      template isGraph(G)
>      {
>          isGraph = __traits(hasMember, G, "directed") &&
> isBoolean!(typeof(G.directed));
>      }
>
> ... which I'd assumed would cover the case where G does not have a
> member "directed".  But in fact if I pass it a struct that does not have
> the entity .directed defined therein, it will return an error:  "no
> property 'directed' for type ..."
>
> So, can someone give me a good idea of how to go about writing such a
> compile-time template that checks (i) for the existence of certain
> methods/functions/members and (ii) confirms their characteristics, such
> as their return values or arguments?
>
> Thanks & best wishes,
>
>      -- Joe

You need to put it into a static if, otherwise the compiler will continue semantic checks on the second part of the expression. E.g.

template isGraph(G)
{
  static if(__traits(hasMember, G, "directed"))
    enum bool isGraph = isBoolean!(typeof(G.directed));
  else
    enum bool isGraph = false;
}
August 30, 2013
On 30/08/13 21:40, Benjamin Thaut wrote:
> You need to put it into a static if, otherwise the compiler will continue
> semantic checks on the second part of the expression. E.g.
>
> template isGraph(G)
> {
>    static if(__traits(hasMember, G, "directed"))
>      enum bool isGraph = isBoolean!(typeof(G.directed));
>    else
>      enum bool isGraph = false;
> }

Ahh, right, thanks. :-)

Is there a recommended way for handling the case where there are many such members -- say about 10 or more?  The static if's could become very highly nested with this approach.  I suppose I could go through like this:

    static if(!__traits(hasMember, G, "one"))
        enum bool isGraph = false;
    else static if(!__traits(hasMember, G, "two"))
        enum bool isGraph = false;
    else static if ...
        ...
    else
    {
        // Now I know all the members exist and I can
        // start checking out their properties ...
    }

August 30, 2013
On 08/30/2013 01:57 PM, Joseph Rushton Wakeling wrote:
> On 30/08/13 21:40, Benjamin Thaut wrote:
>> You need to put it into a static if, otherwise the compiler will continue
>> semantic checks on the second part of the expression. E.g.
>>
>> template isGraph(G)
>> {
>>    static if(__traits(hasMember, G, "directed"))
>>      enum bool isGraph = isBoolean!(typeof(G.directed));
>>    else
>>      enum bool isGraph = false;
>> }
>
> Ahh, right, thanks. :-)
>
> Is there a recommended way for handling the case where there are many
> such members -- say about 10 or more?  The static if's could become very
> highly nested with this approach.  I suppose I could go through like this:
>
>      static if(!__traits(hasMember, G, "one"))
>          enum bool isGraph = false;
>      else static if(!__traits(hasMember, G, "two"))
>          enum bool isGraph = false;
>      else static if ...
>          ...
>      else
>      {
>          // Now I know all the members exist and I can
>          // start checking out their properties ...
>      }
>

How about allSatisfy:

  http://dlang.org/phobos/std_typetuple.html#.allSatisfy

Ali

August 30, 2013
On 30/08/13 23:06, Ali Çehreli wrote:
> How about allSatisfy:
>
>    http://dlang.org/phobos/std_typetuple.html#.allSatisfy

I'll have a look at that, thanks :-)

Here's what I came up with, for now.  It should probably be extended with further tests on the characteristics of the various member functions but it seems to be sufficient for now:

////////////////////////////////////////////////////////

template isGraph(G)
{
    static if (!__traits(hasMember, G, "directed") ||
               !__traits(hasMember, G, "edge") ||
               !__traits(hasMember, G, "edgeCount") ||
               !__traits(hasMember, G, "vertexCount") ||
               !__traits(hasMember, G, "isEdge") ||
               !__traits(hasMember, G, "edgeID") ||
               !__traits(hasMember, G, "addEdge") ||
               !__traits(hasMember, G, "degreeIn") ||
               !__traits(hasMember, G, "degreeOut") ||
               !__traits(hasMember, G, "incidentEdgesIn") ||
               !__traits(hasMember, G, "incidentEdgesOut") ||
               !__traits(hasMember, G, "neighboursIn") ||
               !__traits(hasMember, G, "neighboursOut"))
    {
        enum bool isGraph = false;
    }
    else static if (!isBoolean!(typeof(G.directed)))
    {
        enum bool isGraph = false;
    }
    else static if (G.directed && (__traits(hasMember, G, "degree") ||
                                   __traits(hasMember, G, "incidentEdges") ||
                                   __traits(hasMember, G, "neighbours")))
    {
        enum bool isGraph = false;
    }
    else static if (!G.directed && (!__traits(hasMember, G, "degree") ||
                                    !__traits(hasMember, G, "incidentEdges") ||
                                    !__traits(hasMember, G, "neighbours")))
    {
        enum bool isGraph = false;
    }
    else
    {
        enum bool isGraph = true;
    }
}

////////////////////////////////////////////////////////
August 30, 2013
Joseph Rushton Wakeling:

>     static if (!__traits(hasMember, G, "directed") ||
>                !__traits(hasMember, G, "edge") ||
>                !__traits(hasMember, G, "edgeCount") ||
>                !__traits(hasMember, G, "vertexCount") ||
>                !__traits(hasMember, G, "isEdge") ||
>                !__traits(hasMember, G, "edgeID") ||
>                !__traits(hasMember, G, "addEdge") ||
>                !__traits(hasMember, G, "degreeIn") ||
>                !__traits(hasMember, G, "degreeOut") ||
>                !__traits(hasMember, G, "incidentEdgesIn") ||
>                !__traits(hasMember, G, "incidentEdgesOut") ||
>                !__traits(hasMember, G, "neighboursIn") ||
>                !__traits(hasMember, G, "neighboursOut"))

Perhaps can shorten that code writing a hasMembers helper (and I suggest to keep those names sorted):

static if (hasMembers!(G, "addEdge
                           degreeIn
                           ...
                           vertexCount".split) {

Bye,
bearophile
August 30, 2013
On Fri, Aug 30, 2013 at 11:51:37PM +0200, bearophile wrote:
> Joseph Rushton Wakeling:
> 
> >    static if (!__traits(hasMember, G, "directed") ||
> >               !__traits(hasMember, G, "edge") ||
> >               !__traits(hasMember, G, "edgeCount") ||
> >               !__traits(hasMember, G, "vertexCount") ||
> >               !__traits(hasMember, G, "isEdge") ||
> >               !__traits(hasMember, G, "edgeID") ||
> >               !__traits(hasMember, G, "addEdge") ||
> >               !__traits(hasMember, G, "degreeIn") ||
> >               !__traits(hasMember, G, "degreeOut") ||
> >               !__traits(hasMember, G, "incidentEdgesIn") ||
> >               !__traits(hasMember, G, "incidentEdgesOut") ||
> >               !__traits(hasMember, G, "neighboursIn") ||
> >               !__traits(hasMember, G, "neighboursOut"))
> 
> Perhaps can shorten that code writing a hasMembers helper (and I
> suggest to keep those names sorted):
[...]

Here's a first stab at a possible implementation:

	/* Warning: untested code */

	import std.typetuple : allSatisfy;

	template isString(T) {
		// There may already be something in Phobos that does
		// this, but I'm too lazy to look.
		enum isString = is(T == string);
	}

	template hasMembers(alias T, Members...)
		if (allSatisfy!isString(Members))
	{
		// Template recursion + exprTuple slicing FTW :)
		enum hasMembers = __traits(hasMember, T, Members[0]) &&
			hasMembers!(T, Members[1..$]);
	}

	void myFunc(G)(G graph)
	{
		static if (hasMembers!(G, "directed", "edge", /* ... */))
		{
			...
		}
	}


T

-- 
Computers shouldn't beep through the keyhole.
August 30, 2013
On 30/08/13 23:51, bearophile wrote:
> Perhaps can shorten that code writing a hasMembers helper (and I suggest to keep
> those names sorted)

Nice thought, I might look into that at some point. :-)