23

According to the n4296 C++ standard document:

[dcl.init.list] (8.5.4.4) (pg223-224)

Within the initializer-list of a braced-init-list, the initializer-clauses, including any that result from pack expansions (14.5.3), are evaluated in the order in which they appear. That is, every value computation and side effect associated with a given initializer-clause is sequenced before every value computation and side effect associated with any initializer-clause that follows it in the comma-separated list of the initializer-list. [Note: This evaluation ordering holds regardless of the semantics of the initialization; for example, it applies when the elements of the initializer-list are interpreted as arguments of a constructor call, even though ordinarily there are no sequencing constraints on the arguments of a call. —end note ]

(emphasis mine)

The note was added here: http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1030

This reads to me that the following code:

#include <iostream>

struct MyType {
  MyType(int i, int j, int k, int l)
    : sum(i + j + k + l)
  {

  }

  int sum;
};

int main()
{
  int i = 0;
  std::cout << MyType{ ++i, ++i, ++i, ++i }.sum << '\n';
}

Should print "10".

This is my reasoning:

  • MyType is being initialized via a braced-init-list
  • braced-init-lists are evaluated in order
  • even when it is interpreted as arguments of a constructor call
  • this means that it should be evaluated as MyType(1,2,3,4)

That is to say, the above code should behave exactly like this code:

#include <initializer_list>
#include <iostream>

int main()
{
  int i = 0;
  std::initializer_list<int> il{++i, ++i, ++i, ++i};
  std::cout << *il.begin() + *(il.begin() + 1) + *(il.begin() + 2) + *(il.begin() + 3) << '\n';
}

But it does not. The first example prints '16' and the second example prints '10'

Literally every compiler from every vendor that I can get my hands on prints '16', seemingly ignoring that part of the standard and not inserting sequence points.

What am I missing here?

Note: The following seem to be related to this question:

Community
  • 1
  • 1
lefticus
  • 3,346
  • 2
  • 24
  • 28
  • Which compilers have you tried? – K-ballo Apr 25 '16 at 22:46
  • It produces 16 for me with gcc version 5.3.0 (i686-posix-dwarf-rev0, Built by MinGW-W64 project) – Jerry Jeremiah Apr 25 '16 at 22:54
  • 1
    N4296 is not "the standard", it is a working draft in the C++17 standardization process – M.M Apr 25 '16 at 22:55
  • MSVC 2015, GCC 4.7.3, 4.8.1, 4.8.2, 4.9.0, 4.9.2, 5.1.0, 5.2.0, 5.3.0, 6.0.0. Upon double checking, clang seems to do the right thing, but does break in a slightly modified scenario. – lefticus Apr 25 '16 at 22:56
  • 1
    On godbolt, all clang versions give `10` and all gcc versions give `16`, so perhaps it is a gcc bug – M.M Apr 25 '16 at 23:02
  • @M.M I double checked, and this wording also exists in http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3797.pdf which is the standard for C++14 – lefticus Apr 25 '16 at 23:07
  • @lefticus N4140 is C++14. N3797 was some time before it. – M.M Apr 25 '16 at 23:08
  • If it works in clang then is there a bug report for gcc? – Jerry Jeremiah Apr 25 '16 at 23:09
  • @M.M so the wording in question as been around for quite some time. Pre C++14 at least. – lefticus Apr 25 '16 at 23:09
  • 2
    Reported as https://gcc.gnu.org/bugzilla/show_bug.cgi?id=70796. – T.C. Apr 26 '16 at 05:43
  • My understanding is that C++14 is between n3936 and n4140. I'm honestly not sure which of the two is closer to the C++14 IS. The snippet quoted in the question is in both n3936 and n4140. – Michael Burr Apr 27 '16 at 13:15
  • 1
    @T.C: I understand from your comment #2 on the bug report that there are two levels here: (1) the order of evaluation of initializers in curly braces list is the one in the source code, but (2) that isn't necessarily the order in which the results are then subsequently converted to rvalues? If so then that is very surprising behavior and to my mind probably unintended by the committee. I.e. a possible core language defect here? – Cheers and hth. - Alf Jun 18 '16 at 07:02

2 Answers2

7

The answer seems to be that yes, this is a bug in both GCC and MSVC.

This is the status of this issue:

  1. There are several bugs against GCC regarding init-list rules. Most of them have gone completely unacknowledged by the GCC team. This at least implies that G++ does have bugs here because the issues have not been closed as invalid
  2. I have received unofficial word from the MSVC compiler team that this is in fact a bug in their compiler and they are working internally to fix it. However, I have no external bug to point to. As of MSVC 2015 Update 3, the old behavior remains.
  3. Clang, which at this point is by far the most pedantic standards-complaint compiler, implements it the way the standard seems to read.

My personal investigation, discussions with C++ experts at conferences, and unofficial answers I've received from compiler developers indicates that this is a bug in MSVC and GCC, but I'm always reluctant to answer my own questions on StackOverflow. But here we are.

lefticus
  • 3,346
  • 2
  • 24
  • 28
0

This note is not related to evaluation order. As it was stated in one of comments, it's about order of converting actual parameters to rvalues and standard does not define such order. You should receive following warning (gcc):

17:58: warning: operation on 'i' may be undefined [-Wsequence-point]

I modified a program slightly to demonstrate how evaluation of arguments work with {} and ().

With such modification, program does not depend on order of converting lvalue to rvalue, thus does not have ambiguity which disappointed you.

#include <iostream>

struct MyType {
  MyType(int i, int j)
    : sum(i + j)
  {

  }

  int sum;
};

int main()
{
  int i = 0;
  int a,b;
  std::cout << MyType{ (a = ++i), (b = ++i) }.sum << '\n';
  std::cout << "Here clauses are evaluated in order they appear: a=" << a << ", b=" << b << std::endl;
  i = 0;
  std::cout << MyType( (a = ++i), (b = ++i) ).sum << '\n';
  std::cout << "Here order of evaluation depends on implementation: a=" << a << ", b=" << b << std::endl;
}

And the output of this program for clang and gcc:

clang:

3
Here clauses are evaluated in order they appear: a=1, b=2
3
Here order of evaluation depends on implementation: a=1, b=2

gcc:

3
Here clauses are evaluated in order they appear: a=1, b=2
3
Here order of evaluation depends on implementation: a=2, b=1

As you can see, in case of curly brackets, clauses are evaluated in order of appearance under both compilers, which corresponds to the note you provided.

Oleg Oleg
  • 525
  • 6
  • 10
  • No, the question is specifically about sequence points, not about order of evaluation. – lefticus Jul 03 '16 at 20:53
  • You stated: "this means that it should be evaluated as MyType(1,2,3,4)". Yes, it is evaluated such way, but constructor is called as MyType(i, i, i, i) - after that evaluation. And constructor call is not described in note you provided, it is only defined sequencing in evaluation. Thus, you have compilation warning as above. – Oleg Oleg Jul 04 '16 at 13:27
  • You seem to be missing the "Note: This evaluation ordering holds regardless of the semantics of the initialization; for example, it applies when the elements of the initializer-list are interpreted as arguments of a constructor call, even though ordinarily there are no sequencing constraints on the arguments of a call. —end note" portion of the original posting, which explicitly states "constructor call." – lefticus Jul 05 '16 at 14:04