D issues are now tracked on GitHub. This Bugzilla instance remains as a read-only archive.
Issue 5749 - (D1 only) argument evaluation order of chained function from right
Summary: (D1 only) argument evaluation order of chained function from right
Status: RESOLVED WORKSFORME
Alias: None
Product: D
Classification: Unclassified
Component: dmd (show other issues)
Version: D1 (retired)
Hardware: Other All
: P2 major
Assignee: No Owner
URL:
Keywords: pull, wrong-code
Depends on:
Blocks:
 
Reported: 2011-03-18 12:27 UTC by Fawzi Mohamed
Modified: 2018-10-22 04:08 UTC (History)
5 users (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this issue.
Description Fawzi Mohamed 2011-03-18 12:27:28 UTC
I was surprised by the avaluation order of opCall arguments:

    extern(C) int printf(/+const +/char*,...);

    struct A{
      A opCall(int i){
        printf("i=%d\n".ptr,i);
        //return this;
        return *this;
      }
    }

    void main(){
      A a;
      size_t  i;
      a(++i)(++i);
    }


prints

i=2
i=1

because the arguments are evaluated from right.
I found it vers surprising, I would have expected to have them evaluated from left to right.
This "breaks" whisper style chaining.
I am not sure if/when this behaviour is documented/useful, but I would change it
Comment 1 Jonathan M Davis 2011-03-18 12:59:08 UTC
The order of function arguments is _never_ defined. It's completely implementation dependent. The compiler is free to re-order them as it wishes. So, for instance, using a variable and incrementing it separately in the same expression (or incrementing it two places like you're doing) is definitely a bad idea. It's always been that way in C and C++, and it's that way in D.

Walter has said that he may make it so that the order _is_ defined, which would eliminate bugs related to someone doing something like you're trying to do, but that change has never been made.

Regardless, the behavior is completely expected. Don't rely on the order of evalution of function arguments.
Comment 2 Steven Schveighoffer 2011-03-18 13:17:57 UTC
That's true for arguments to a single function.  But what about arguments to different functions in the same expression?  Considering that you MUST call the functions in left-to-right order given because the next function in the chain depends on the result of the previous.

I'm not saying the current behavior is wrong or not expected, but it feels weird when one parameter is evaluated left to right (the struct reference) and the other parameter is evaluated right to left *across the expression*.

For example, if I do:

a(1)(2);

it prints
i=1
i=2

I would expect what Fawzi did.  It would be nice to get an explanation of why it works this way, if it is intentional.

What would you expect for this:

size_t inc(ref size_t i) { return ++i; }

a(inc(i))(inc(i));

would you expect it to first run both incs, caching the return values and then using that to call a?  Because that's what it does.

Also, this is weird too:

struct A
{
  A opCall(ref size_t i)
  {
    printf("i=%d\n", i);
  }
}

a(++i)(++i);

prints:

i=2
i=2

If I change i to a large struct, then the prospect of pushing multiple copies of that large struct on the stack so I can cache the results becomes alarming.
Comment 3 Steven Schveighoffer 2011-03-18 13:37:35 UTC
If you consider that 'this' is the first argument to the opCall, you would expect this to do the same thing:

A blah(A a, size_t i)
{
   printf("i=%d\n", i);
   return a;
}

blah(blah(a, ++i), ++i);

but it does print:

i=1
i=2

So there is something definitely inconsistent here.
Comment 4 Fawzi Mohamed 2011-03-18 13:59:28 UTC
clearly about this I agree with Steven, the "correct" (or expected if you prefer) evaluation should be to evaluate the arguments as late as possible.

a(b)(c); is the same as (a(b))(c) I would expect c not to be evaluated before a(b) is fully evaluated (both b *and* a(b)), otherwise whisper style is broken, and is counterintuitive.
Comment 5 Martin Nowak 2011-09-07 15:53:41 UTC
The result also changes with respect to enabling -inline or not.
Comment 7 bearophile_hugs 2013-11-25 17:48:47 UTC
This patch seems important.
Comment 8 Kenji Hara 2013-11-25 18:28:48 UTC
(In reply to comment #7)
> This patch seems important.

The root issue is very similar to bug 8396. When method call is used, the side effect of callable entity (evaluation of 'this' object + getting member function pointer) is not ordered before the arguments evaluation.

I think this is not intended behavior. For example, this code evaluates function arguments left-to-right.

extern (C) int printf(const char*, ...);
void main()
{
    void delegate(int) foo(int i)
    {
        printf("i: %d\n", i);
        return (a){ foo(a); };
    }
    auto getFunc() { return &foo; }

    int i;
    getFunc()(++i)(++i);
    // getFunc() is evaluated before the first ++i expression,
    // and getFunc()(++i) is evaluated before the second ++i expression.
}

Because l-to-r evaluation is enforced by the glue-layer code
https://github.com/D-Programming-Language/dmd/blob/7360ae8611add4dc0a89cd870a6ac6490fb2a19b/src/e2ir.c#L3706

The code was introduce by the commit:
https://github.com/D-Programming-Language/dmd/commit/80e2319878bee3bb139a20c0bc1b85b1ec04b892

If you comment out the part, above example code will be broken.

Therefore, I'll change this issue to 'wrong-code' bug.
Comment 9 github-bugzilla 2014-01-04 22:27:55 UTC
Commits pushed to master at https://github.com/D-Programming-Language/dmd

https://github.com/D-Programming-Language/dmd/commit/7a31af4b65667fa6a993743f493e8d5f156e0d30
fix Issue 5749 - argument evaluation order of chained function from right

https://github.com/D-Programming-Language/dmd/commit/d9c783cac6d06b7be8b5888e574694aec426a470
Merge pull request #2881 from 9rnsr/fix5749

Issue 5749 - argument evaluation order of chained function from right
Comment 10 Kenji Hara 2014-01-04 22:32:49 UTC
Fixed in D2.
Comment 11 Mathias LANG 2018-10-22 04:08:57 UTC
> Fixed in D2.

And so, finally closing this.