www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - hasDataMember and mixin inconsistency

reply "Olivier Grant" <olivier.grant gmail.com> writes:
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.
Jan 26 2013
parent reply Philippe Sigaud <philippe.sigaud gmail.com> writes:
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.
Jan 27 2013
parent reply "Olivier Grant" <olivier.grant gmail.com> writes:
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.
Jan 27 2013
next sibling parent Artur Skawina <art.08.09 gmail.com> writes:
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
Jan 27 2013
prev sibling parent reply Philippe Sigaud <philippe.sigaud gmail.com> writes:
 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?
Jan 27 2013
parent reply "Olivier Grant" <olivier.grant gmail.com> writes:
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.
Jan 27 2013
parent reply Philippe Sigaud <philippe.sigaud gmail.com> writes:
 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.
Jan 27 2013
parent reply "Olivier Grant" <olivier.grant gmail.com> writes:
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.
Jan 28 2013
parent reply Philippe Sigaud <philippe.sigaud gmail.com> writes:
 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!
Jan 28 2013
parent "Olivier Grant" <olivier.grant gmail.com> writes:
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.
Jan 29 2013