www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - lazy evaluation of logical operators in enum definition

reply Shachar Shemesh <shachar weka.io> writes:
Consider the following program:

struct S1 {
     enum member = 3;
}

struct S2 {
     enum member = 2;
}

struct S3 {
}

enum prop(T) = __traits(hasMember, T, "member") && T.member==3;

pragma(msg, prop!S1);
pragma(msg, prop!S2);
pragma(msg, prop!S3);

When compiled, it produces:
true
false
test.d(12): Error: no property member for type S3
test.d(16): Error: template instance `test.prop!(S3)` error instantiating
test.d(16):        while evaluating pragma(msg, prop!(S3))

If I change the definition of "prop" to:
template prop(T) {
     static if( __traits(hasMember, T, "member") && T.member==3 )
         enum prop = true;
     else
         enum prop = false;
}

then everything compiles as expected.

It seems that the && evaluation does not stop when the first false is found.
Apr 15 2018
next sibling parent Basile B. <b2.temp gmx.com> writes:
On Monday, 16 April 2018 at 05:57:01 UTC, Shachar Shemesh wrote:
 Consider the following program:

 struct S1 {
     enum member = 3;
 }

 struct S2 {
     enum member = 2;
 }

 struct S3 {
 }

 enum prop(T) = __traits(hasMember, T, "member") && T.member==3;

 pragma(msg, prop!S1);
 pragma(msg, prop!S2);
 pragma(msg, prop!S3);

 When compiled, it produces:
 true
 false
 test.d(12): Error: no property member for type S3
 test.d(16): Error: template instance `test.prop!(S3)` error 
 instantiating
 test.d(16):        while evaluating pragma(msg, prop!(S3))

 If I change the definition of "prop" to:
 template prop(T) {
     static if( __traits(hasMember, T, "member") && T.member==3 )
         enum prop = true;
     else
         enum prop = false;
 }

 then everything compiles as expected.

 It seems that the && evaluation does not stop when the first 
 false is found.
Hello, i've encountered a similar issue recently (see https://issues.dlang.org/show_bug.cgi?id=18115#c13). There are explanations in the last comments.
Apr 15 2018
prev sibling next sibling parent Jacob Carlborg <doob me.com> writes:
On Monday, 16 April 2018 at 05:57:01 UTC, Shachar Shemesh wrote:
 Consider the following program:

 struct S1 {
     enum member = 3;
 }

 struct S2 {
     enum member = 2;
 }

 struct S3 {
 }

 enum prop(T) = __traits(hasMember, T, "member") && T.member==3;

 pragma(msg, prop!S1);
 pragma(msg, prop!S2);
 pragma(msg, prop!S3);

 When compiled, it produces:
 true
 false
 test.d(12): Error: no property member for type S3
 test.d(16): Error: template instance `test.prop!(S3)` error 
 instantiating
 test.d(16):        while evaluating pragma(msg, prop!(S3))

 If I change the definition of "prop" to:
 template prop(T) {
     static if( __traits(hasMember, T, "member") && T.member==3 )
         enum prop = true;
     else
         enum prop = false;
 }

 then everything compiles as expected.

 It seems that the && evaluation does not stop when the first 
 false is found.
I think the issue is better illustrated with a function: bool prop(T)() { if (__traits(hasMember, T, "member")) { if (T.member == 3) return true; } return false; } In this function there's no way to tell if the function is going to be executed at compile time or at runtime. Therefore the semantics of the whole function need to be valid. Replacing the `if` with a `static if` would solve the problem, as you mentioned. This works because the semantic analysis of `static if` and CTFE evaluation of a function occurs at different phases in the compiler. This post explains this better and in more detail [1]. The issue is that the compiler will do the semantic analysis of `T.member` before it has run CTFE or constant folding on the expression. Although, one could argue that in your case it's clear that the expression only will be evaluated at compile time, but that's not how the compiler works currently. [1] https://wiki.dlang.org/User:Quickfur/Compile-time_vs._compile-time -- /Jacob Carlborg
Apr 16 2018
prev sibling next sibling parent reply Simen =?UTF-8?B?S2rDpnLDpXM=?= <simen.kjaras gmail.com> writes:
On Monday, 16 April 2018 at 05:57:01 UTC, Shachar Shemesh wrote:
 Consider the following program:

 struct S1 {
     enum member = 3;
 }

 struct S2 {
     enum member = 2;
 }

 struct S3 {
 }

 enum prop(T) = __traits(hasMember, T, "member") && T.member==3;

 pragma(msg, prop!S1);
 pragma(msg, prop!S2);
 pragma(msg, prop!S3);

 When compiled, it produces:
 true
 false
 test.d(12): Error: no property member for type S3
 test.d(16): Error: template instance `test.prop!(S3)` error 
 instantiating
 test.d(16):        while evaluating pragma(msg, prop!(S3))

 If I change the definition of "prop" to:
 template prop(T) {
     static if( __traits(hasMember, T, "member") && T.member==3 )
         enum prop = true;
     else
         enum prop = false;
 }

 then everything compiles as expected.

 It seems that the && evaluation does not stop when the first 
 false is found.
There's a kinda neat (and kinda ugly) way to implement prop in one line: enum prop(T) = __traits(compiles, { static assert(T.member == 3); }); Now, that's not the same as short-circuiting, and only useful in some cases, but for those cases, it's useful. -- Simen
Apr 17 2018
parent reply Shachar Shemesh <shachar weka.io> writes:
On 17/04/18 13:59, Simen Kjærås wrote:
 There's a kinda neat (and kinda ugly) way to implement prop in one line:
 
      enum prop(T) = __traits(compiles, { static assert(T.member == 3); });
 
 Now, that's not the same as short-circuiting, and only useful in some 
 cases, but for those cases, it's useful.
Also, extremely dangerous. Seriously, guys and gals. __traits(compiles) (and its uglier sibling, is(typeof())) should be used *extremely* sparingly. The problem is that just about any use of __traits(compiles) I know of is seeking to weed out one particular reason for the compilation failure. There is a known bug in D, however, that the compiler consistently fails to read the programmer's mind. The compiler only knows that the code as provided does not compile, and that in that case you asked for something to happen. The usual outcome is that 80-90% of the times your code does what you expect, but then something comes along that throws you off the beaten track. In those cases, instead of getting an error message, you get one of the sides of the "if", which results in random behavior by your code. If you're lucky, you will get an error message much further down the compilation line, and then start having a fun day of trying to figure code will actually compile. My personal rule of thumb is this: If there is *any* way of achieving the result I want without __traits(compiles), do it that way. Shachar
Apr 17 2018
parent reply Atila Neves <atila.neves gmail.com> writes:
On Wednesday, 18 April 2018 at 04:44:23 UTC, Shachar Shemesh 
wrote:
 On 17/04/18 13:59, Simen Kjærås wrote:
 [...]
Also, extremely dangerous. Seriously, guys and gals. __traits(compiles) (and its uglier sibling, is(typeof())) should be used *extremely* sparingly. [...]
A very good rule of thumb. I've lost of how many times I've had a bug because of __traits(compiles) being false, but not in the way I expected it to be!
Apr 18 2018
parent Meta <jared771 gmail.com> writes:
On Wednesday, 18 April 2018 at 10:19:20 UTC, Atila Neves wrote:
 On Wednesday, 18 April 2018 at 04:44:23 UTC, Shachar Shemesh 
 wrote:
 On 17/04/18 13:59, Simen Kjærås wrote:
 [...]
Also, extremely dangerous. Seriously, guys and gals. __traits(compiles) (and its uglier sibling, is(typeof())) should be used *extremely* sparingly. [...]
A very good rule of thumb. I've lost of how many times I've had a bug because of __traits(compiles) being false, but not in the way I expected it to be!
Let me third that. Although this would not be as big of a problem if we had a way of printing out the values for failed template constraints (isn't this already done for static if now?)
Apr 18 2018
prev sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 4/15/2018 10:57 PM, Shachar Shemesh wrote:
 It seems that the && evaluation does not stop when the first false is found.
Evaluation does stop, semantic analysis does not. For example: bool foo() { return 0 && undefined_variable; } does not compile. I'd speculate that most would consider this code compiling successfully as surprising behavior. It would also be difficult to specify, as just when is e1 of (e1 && e2) statically known at compile time (i.e. how much flow analysis is the compiler expected to do to determine this?).
Apr 18 2018
parent Timon Gehr <timon.gehr gmx.ch> writes:
On 18.04.2018 09:18, Walter Bright wrote:
 On 4/15/2018 10:57 PM, Shachar Shemesh wrote:
 It seems that the && evaluation does not stop when the first false is 
 found.
Evaluation does stop, semantic analysis does not. For example:   bool foo() {     return 0 && undefined_variable;   } does not compile. I'd speculate that most would consider this code compiling successfully as surprising behavior. It would also be difficult to specify, as just when is e1 of (e1 && e2) statically known at compile time (i.e. how much flow analysis is the compiler expected to do to determine this?).
His use case is `enum x = 0 && undefined_variable;`, for which at least your second concern does not apply. It is also surprising that a `static if` condition cannot be hoisted out: --- static if(expression){ ... } --- --- // not necessarily the same as the above enum c = expression; static if(c){ ... } --- I think the suggestion is to do lazy semantic analysis for all standalone expressions that need to be evaluated at compile-time (and not just for `static if`/`static assert` conditions).
Apr 18 2018