By walnut


2019-10-02 15:08:09 8 Comments

Consider the following program (and its alternative in the comment) in C++17:

#include<iostream>

void a(int) {
    std::cout << "a\n";
}

void b(int) {
   std::cout << "b\n";
}

int main() {
    using T = void(*)(int);

    T f = a;
    (T(f))((f=b,0)); // alternatively: f((f=b,0))
}

With -O2 option, Clang 9.0.0 prints a and GCC 9.2 prints b. Both warn me about unsequenced modification and access to f. See godbolt.org.

My expectation was that this is program has well-defined behavior and will print a, because C++17 guarantees that the left-hand expression of the call (T(f)) is sequenced before any evaluation of the arguments. Because the result of the expression (T(f)) is a new pointer to a, the later modification of f should have no impact on the call at all. Am I wrong?

Both compilers give the same output if I use f((f=b,0)); instead of (T(f))((f=b,0));. Here I am slightly unsure about the undefined behavior aspect. Would this be undefined behavior because f still refers to the declared function pointer after evaluation, which will have been modified by the evaluation of the arguments and if so, why exactly would that cause undefined behavior rather than calling b?

I have asked a related question with regards to order of evaluation of non-static member function calls in C++17 here. I am aware that writing code like this is dangerous and unnecessary, but I want to understand the details of the C++ standard better.

Edit: GCC trunk now also prints a after the bug filed by Barry (see his answer below) has been fixed. Both Clang and GCC trunk do still show false-positive warnings with -Wall, though.

1 comments

@Barry 2019-10-02 16:16:48

The C++17 rule is, from [expr.call]/8:

The postfix-expression is sequenced before each expression in the expression-list and any default argument. The initialization of a parameter, including every associated value computation and side effect, is indeterminately sequenced with respect to that of any other parameter.

In (T(f))((f=b,0));, (T(f)) is sequenced before the initialization of the parameter from (f=b, 0). All of this is well-defined and the program should print "a". That is, it should behave just like:

auto __tmp = T(f);
__tmp((f=b, 0));

The same is true even if we change your program such that this were valid:

T{f}(f=b, 0); // two parameters now, instead of one

The f=b and 0 expressions are indeterminately sequenced with each other, but T{f} is still sequenced before both, so this would still invoke a.

Filed 91974.

@walnut 2019-10-02 16:19:51

Thank you for the answer. T(f)(f=b, 0); doesn't work because it will be considered a redefinition of f. I was also really interested in the case f((f=b, 0)). Is there something different happening in that case?

@Deduplicator 2019-10-02 16:52:43

Until C++17, the individual arguments and the postfix-expression were all of them unsequenced.

@Davis Herring 2019-10-06 02:00:27

The T(f) has nothing to do with it: f((f=b,0)) has the same meaning (since C++17, again).

Related Questions

Sponsored Content

13 Answered Questions

[SOLVED] What is a smart pointer and when should I use one?

4 Answered Questions

[SOLVED] What made i = i++ + 1; legal in C++17?

52 Answered Questions

39 Answered Questions

21 Answered Questions

[SOLVED] Why should I use a pointer rather than the object itself?

  • 2014-03-03 11:54:16
  • gEdringer
  • 302254 View
  • 1553 Score
  • 21 Answer
  • Tags:   c++ pointers c++11

5 Answered Questions

11 Answered Questions

[SOLVED] How do function pointers in C work?

  • 2009-05-08 15:49:17
  • Yuval Adam
  • 780468 View
  • 1187 Score
  • 11 Answer
  • Tags:   c function-pointers

1 Answered Questions

[SOLVED] std::variant modification in constexpr

1 Answered Questions

[SOLVED] What are the new features in C++17?

Sponsored Content