Thread overview
hasDataMember and mixin inconsistency
Jan 26, 2013
Olivier Grant
Jan 27, 2013
Philippe Sigaud
Jan 27, 2013
Olivier Grant
Jan 27, 2013
Artur Skawina
Jan 27, 2013
Philippe Sigaud
Jan 27, 2013
Olivier Grant
Jan 27, 2013
Philippe Sigaud
Jan 28, 2013
Olivier Grant
Jan 28, 2013
Philippe Sigaud
Jan 29, 2013
Olivier Grant
January 26, 2013
Hi,

First of all, I am very new to D as I've just been playing around with it for the last week, so I might be missing something very obvious.

I'm trying to write a template that would allow me to determine if a struct or class has a data member with a specific name (similar to what the hasMember template does, but only for data members).

I originally wrote the following template :

[code]
template hasDataMember( T, string M )
{
   enum hasDataMember = __traits(
      compiles,
      mixin("( ref T x, ref T y ){ x." ~ M ~ " = y." ~ M ~ "; }")
   );
}

unittest
{
   struct Test
   {
      struct A { }
      void B( ) { }

      @property A C( ) const { return F; }
      @property long D( ) const { return E; }
      @property void D( long e ) { E = e; }

      long E;
      A F;
   }

   assert(!hasDataMember!(int,"A"));
   assert(!hasDataMember!(int,"init"));

   //assert(!hasDataMember!(Test,"init")); // This fails, why?
   assert(!hasDataMember!(Test,"A"));
   assert(!hasDataMember!(Test,"B"));
   assert(!hasDataMember!(Test,"C"));
   assert( hasDataMember!(Test,"D"));
   assert( hasDataMember!(Test,"E"));
   assert( hasDataMember!(Test,"F"));
}
[/code]

And it works pretty well, but it gets the wrong result for the following test case which is commented out. Originally, I thought maybe you were allowed to write to the .init member of structures, but writing the same code directly without relying on a mixin actually yields the right result (all the asserts pass):

[code]
struct A { int b_; }
assert(!__traits(compiles,( ref int x, ref int y ){ x.init = y.init; }));
assert(!__traits(compiles,( ref Test0 x, ref Test0 y ){ x.init = y.init; }));
assert( __traits(compiles,( ref Test0 x, ref Test0 y ){ x.b_ = y.b_; }));
[/code]

So I have two questions :
1) Why is there this inconsistency between the mixin and direct version ?
2) Is there a better way to check for the existence of a data member ?

Best regards,

Olivier.

January 27, 2013
Hi,

If think in your code, your testing whether or a not a mixin("...") statement is valid D. Which it is.

I'd put the mixin externally:

template hasDataMember( T, string M )
{
   mixin("
   enum hasDataMember = __traits(
      compiles,
      ( ref T x, ref T y ){ x." ~ M ~ " = y." ~ M ~ "; }
   );");
}

Also, I suppose the Test inner struct is not visible from the hasDataMember template. The template is instantiated where it's declared, not where it's called. You could use a mixin template, I guess.

I'd use a string mixin, but then I was converted to string mixins a few years ago :)

string hasDataMember( T )(string M )
{
    return " __traits(compiles, {
        Test t;
        auto _ = t.D; // reading t.M
        t." ~ M ~ " = t." ~ M ~ "; // assign to t.M
    })";
}

using is a bit more noisy than your solution: mixin(hadDataMember!(Test)("M"))


> And it works pretty well, but it gets the wrong result for the following test case which is commented out. Originally, I thought maybe you were allowed to write to the .init member of structures, but writing the same code directly without relying on a mixin actually yields the right result (all the asserts pass)

You cannot write to .init, it's not a member. It's a built-in property, like .sizeof or .offsetof.


> 2) Is there a better way to check for the existence of a data member ?

If by data member, you mean some symbol that can be read and written to, then I'd test just that. See the string mixin before: it tests for existence, reading and writing.
January 27, 2013
On Sunday, 27 January 2013 at 09:49:33 UTC, Philippe Sigaud wrote:
> Hi,
>
> If think in your code, your testing whether or a not a mixin("...")
> statement is valid D. Which it is.

But what I'm surprised by is that the behavior of hasDataMember
with my implementation works fine for all test cases except
hasDataMember!(Test, "init"). If it was only testing whether the
mixin statement was well formed or not, shouldn't my
implementation of hasDataMember always return true ? What's more
confusing is that it seems to work properly for all test cases
except a built-in property of a structure :

assert(!hasDataMember!(long, "init")); // This succeeds.
assert(!hasDataMember!(Test, "init")); // This fails.

> I'd put the mixin externally:
>
> template hasDataMember( T, string M )
> {
>    mixin("
>    enum hasDataMember = __traits(
>       compiles,
>       ( ref T x, ref T y ){ x." ~ M ~ " = y." ~ M ~ "; }
>    );");
> }

I've just tried that and it unfortunately does not work, the same
test case still fails.

> Also, I suppose the Test inner struct is not visible from the
> hasDataMember template. The template is instantiated where it's
> declared, not where it's called. You could use a mixin template, I
> guess.

I'm not sure at all what you mean by that. I thought all symbols
within a source file were visible irrespective of their order or
scope? Also, the test failure is on the Test structure directly,
not its inner structure A.

> I'd use a string mixin, but then I was converted to string mixins a
> few years ago :)
>
> string hasDataMember( T )(string M )
> {
>     return " __traits(compiles, {
>         Test t;
>         auto _ = t.D; // reading t.M
>         t." ~ M ~ " = t." ~ M ~ "; // assign to t.M
>     })";
> }
>
> using is a bit more noisy than your solution: mixin(hadDataMember!(Test)("M"))

I've tried that as well and it still fails on the same test case
again.

>> And it works pretty well, but it gets the wrong result for the following
>> test case which is commented out. Originally, I thought maybe you were
>> allowed to write to the .init member of structures, but writing the same
>> code directly without relying on a mixin actually yields the right result
>> (all the asserts pass)
>
> You cannot write to .init, it's not a member. It's a built-in
> property, like .sizeof or .offsetof.

Makes sense.

>> 2) Is there a better way to check for the existence of a data member ?
>
> If by data member, you mean some symbol that can be read and written
> to, then I'd test just that. See the string mixin before: it tests for
> existence, reading and writing.

Could this be a compiler bug by any chance? It seems really weird
that the template would work for intrinsic types but not for
structure, especially when the exact same template without the
use of mixin works fine.

Thanks again for your help.
January 27, 2013
On 01/27/13 12:47, Olivier Grant wrote:
> On Sunday, 27 January 2013 at 09:49:33 UTC, Philippe Sigaud wrote:
>> You cannot write to .init, it's not a member. It's a built-in property, like .sizeof or .offsetof.
> 
> Makes sense.

It does, but apparently the compiler disagrees.

>>> 2) Is there a better way to check for the existence of a data member ?
>>
>> If by data member, you mean some symbol that can be read and written to, then I'd test just that. See the string mixin before: it tests for existence, reading and writing.
> 
> Could this be a compiler bug by any chance? It seems really weird

Yes, it's a bug. Assignments to .init do not make sense and shouldn't be allowed. I just tried, and the old gdc version i have here doesn't flag them as errors (but did segfault after processing one ;) ).

artur
January 27, 2013
>> I'd put the mixin externally:
>>
>> template hasDataMember( T, string M )
>> {
>>    mixin("
>>
>>    enum hasDataMember = __traits(
>>       compiles,
>>       ( ref T x, ref T y ){ x." ~ M ~ " = y." ~ M ~ "; }
>>    );");
>> }
>
>
> I've just tried that and it unfortunately does not work, the same test case still fails.

? I'll find the files again, for I tested before posting.

>> Also, I suppose the Test inner struct is not visible from the hasDataMember template. The template is instantiated where it's declared, not where it's called. You could use a mixin template, I guess.
>
>
> I'm not sure at all what you mean by that. I thought all symbols within a source file were visible irrespective of their order or scope? Also, the test failure is on the Test structure directly, not its inner structure A.

Test is inside main() { ... }. I guess it's not visible from the
module inner scope.


>> I'd use a string mixin, but then I was converted to string mixins a few years ago :)
>>
>> string hasDataMember( T )(string M )
>> {
>>     return " __traits(compiles, {
>>         Test t;
>>         auto _ = t.D; // reading t.M
>>         t." ~ M ~ " = t." ~ M ~ "; // assign to t.M
>>     })";
>> }
>>
>> using is a bit more noisy than your solution:
>> mixin(hadDataMember!(Test)("M"))
>
>
> I've tried that as well and it still fails on the same test case again.

?

Here is what I used before posting:

string hasDataMember( T )(string M )
{
    return " __traits(compiles, {
        Test t;
        auto _ = t.D; // reading t.M
        t." ~ M ~ " = t." ~ M ~ "; // assign to t.M
    })";
}

void main()
{
   struct Test
   {
      struct A { }
      void B( ) { }

      @property A C( ) const { return F; }
      @property long D( ) const { return E; }
      @property void D( long e ) { E = e; }

      long E;
      A F;
   }

   assert(!mixin(hasDataMember!(Test)("init")));
   assert(!mixin(hasDataMember!(int)("init")));

   assert(!mixin(hasDataMember!(Test)("init"))); // Passes
   assert(!mixin(hasDataMember!(Test)("A")));
   assert(!mixin(hasDataMember!(Test)("B")));
   assert(!mixin(hasDataMember!(Test)("C")));

   assert(mixin(hasDataMember!(Test)("D")));
   assert(mixin(hasDataMember!(Test)("E")));
   assert(mixin(hasDataMember!(Test)("F")));
}

It seems the right behaviour. Am I mistaken?
January 27, 2013
On Sunday, 27 January 2013 at 12:58:39 UTC, Philippe Sigaud wrote:
>>> I'd put the mixin externally:
>>>
>>> template hasDataMember( T, string M )
>>> {
>>>    mixin("
>>>
>>>    enum hasDataMember = __traits(
>>>       compiles,
>>>       ( ref T x, ref T y ){ x." ~ M ~ " = y." ~ M ~ "; }
>>>    );");
>>> }
>>
>>
>> I've just tried that and it unfortunately does not work, the same
>> test case still fails.
>
> ? I'll find the files again, for I tested before posting.
>
>>> Also, I suppose the Test inner struct is not visible from the
>>> hasDataMember template. The template is instantiated where it's
>>> declared, not where it's called. You could use a mixin template, I
>>> guess.
>>
>>
>> I'm not sure at all what you mean by that. I thought all symbols
>> within a source file were visible irrespective of their order or
>> scope? Also, the test failure is on the Test structure directly,
>> not its inner structure A.
>
> Test is inside main() { ... }. I guess it's not visible from the
> module inner scope.
>
>
>>> I'd use a string mixin, but then I was converted to string mixins a
>>> few years ago :)
>>>
>>> string hasDataMember( T )(string M )
>>> {
>>>     return " __traits(compiles, {
>>>         Test t;
>>>         auto _ = t.D; // reading t.M
>>>         t." ~ M ~ " = t." ~ M ~ "; // assign to t.M
>>>     })";
>>> }
>>>
>>> using is a bit more noisy than your solution:
>>> mixin(hadDataMember!(Test)("M"))
>>
>>
>> I've tried that as well and it still fails on the same test case
>> again.
>
> ?
>
> Here is what I used before posting:
>
> string hasDataMember( T )(string M )
> {
>     return " __traits(compiles, {
>         Test t;
>         auto _ = t.D; // reading t.M
>         t." ~ M ~ " = t." ~ M ~ "; // assign to t.M
>     })";
> }

What is the purpose of "auto _ = t.D;" ?

> void main()
> {
>    struct Test
>    {
>       struct A { }
>       void B( ) { }
>
>       @property A C( ) const { return F; }
>       @property long D( ) const { return E; }
>       @property void D( long e ) { E = e; }
>
>       long E;
>       A F;
>    }
>
>    assert(!mixin(hasDataMember!(Test)("init"))); // (1) - Fails
>    assert(!mixin(hasDataMember!(int)("init"))); // (2) - Fails
>
>    assert(!mixin(hasDataMember!(Test)("A")));
>    assert(!mixin(hasDataMember!(Test)("B")));
>    assert(!mixin(hasDataMember!(Test)("C")));
>
>    assert(mixin(hasDataMember!(Test)("D")));
>    assert(mixin(hasDataMember!(Test)("E")));
>    assert(mixin(hasDataMember!(Test)("F")));
> }
>
> It seems the right behaviour. Am I mistaken?

Using dmd 2.060 and command line "rdmd test.d", I get an assertion fail on both calls that check for a member "init" whether for int or Test.
January 27, 2013
>> string hasDataMember( T )(string M )
>> {
>>     return " __traits(compiles, {
>>         Test t;
>>         auto _ = t.D; // reading t.M
>>         t." ~ M ~ " = t." ~ M ~ "; // assign to t.M
>>     })";
>> }
>
>
> What is the purpose of "auto _ = t.D;" ?

Just testing whether t.M can be assigned to something (ie, is it a value?) I use '_' as a variable name to indicate I don't care for it's precise name/value. It's just a placeholder.

> Using dmd 2.060 and command line "rdmd test.d", I get an assertion fail on both calls that check for a member "init" whether for int or Test.

Using 2.061 here.
January 28, 2013
On Sunday, 27 January 2013 at 16:51:26 UTC, Philippe Sigaud wrote:
>>> string hasDataMember( T )(string M )
>>> {
>>>     return " __traits(compiles, {
>>>         Test t;
>>>         auto _ = t.D; // reading t.M
>>>         t." ~ M ~ " = t." ~ M ~ "; // assign to t.M
>>>     })";
>>> }
>>
>>
>> What is the purpose of "auto _ = t.D;" ?
>
> Just testing whether t.M can be assigned to something (ie, is it a value?)
> I use '_' as a variable name to indicate I don't care for it's precise
> name/value. It's just a placeholder.

Ok, I must be missing something, why "t.D" or is this just a typo? And secondly wouldn't "x.M = y.M" take care of checking that the member can be read and written to ? Also, doesn't your solution require T to be default constructible ?

>> Using dmd 2.060 and command line "rdmd test.d", I get an assertion fail on
>> both calls that check for a member "init" whether for int or Test.
>
> Using 2.061 here.

Well that was the problem! I was still using 2.060 which seems to have the bug. Now that I've moved to 2.061 everything works as expected (your implementation and mine).

Thanks,

Olivier.
January 28, 2013
>> Just testing whether t.M can be assigned to something (ie, is it a value?) I use '_' as a variable name to indicate I don't care for it's precise name/value. It's just a placeholder.
>
>
> Ok, I must be missing something, why "t.D" or is this just a typo?

Ark, typo, sorry about that.


> And secondly wouldn't "x.M = y.M" take care of checking that the member can be read and written to ?

t.M = t.M? Yes, you're right.

> Also, doesn't your solution require T to be default constructible ?

Yes, but if a type is not default-constructible, I don't know how to test it. Maybe testing on typeof(T.M)?


>> Using 2.061 here.
>
>
> Well that was the problem! I was still using 2.060 which seems to have the bug. Now that I've moved to 2.061 everything works as expected (your implementation and mine).

Great!
January 29, 2013
On Monday, 28 January 2013 at 20:21:19 UTC, Philippe Sigaud wrote:
>>> Just testing whether t.M can be assigned to something (ie, is it a value?)
>>> I use '_' as a variable name to indicate I don't care for it's precise
>>> name/value. It's just a placeholder.
>>
>>
>> Ok, I must be missing something, why "t.D" or is this just a typo?
>
> Ark, typo, sorry about that.
>
>
>> And secondly wouldn't "x.M = y.M" take care of checking that the member can be
>> read and written to ?
>
> t.M = t.M? Yes, you're right.
>
>> Also, doesn't your solution require T to be default constructible ?
>
> Yes, but if a type is not default-constructible, I don't know how to test it.
> Maybe testing on typeof(T.M)?

My solution to that was to try and compile the definition of a delegate :

__traits(
   compiles,
   "( ref T x ){ x." ~ M ~ " = x." ~ M ~ "; }"
);

I had originally provided two arguments to the delegate (using "( ref T x, ref T y){ x.M = y.M; }") to avoid self assignment but It's not necessary. This seems to work as it returns the right result and does not require T to be default constructible.

Just thought of the fact that I didn't try this with a class (I don't know if you're allowed to write "ref T x" if T == class), but you can always specialize hasDataMember with constraints or use static if.

>>> Using 2.061 here.
>>
>>
>> Well that was the problem! I was still using 2.060 which seems to have the
>> bug. Now that I've moved to 2.061 everything works as expected (your
>> implementation and mine).
>
> Great!

Many thanks for your help on this again.