Thread overview
lazy evaluation of logical operators in enum definition
Apr 16, 2018
Shachar Shemesh
Apr 16, 2018
Basile B.
Apr 17, 2018
Jacob Carlborg
Apr 17, 2018
Simen Kjærås
Apr 18, 2018
Shachar Shemesh
Apr 18, 2018
Atila Neves
Apr 19, 2018
Meta
Apr 18, 2018
Walter Bright
Apr 18, 2018
Timon Gehr
April 16, 2018
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.
April 16, 2018
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.
April 17, 2018
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
April 17, 2018
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
April 18, 2018
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 out what the !*#()%&!@#)!@( just happened. If you're less lucky, the 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
April 18, 2018
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?).
April 18, 2018
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!
April 18, 2018
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).
April 19, 2018
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?)