3

Consider this example:

#include <vector>
#include <stdexcept>

struct A
{
    float a;
    float b;
    float c;
    float d;
};

struct B
{
    A a;
    std::vector<int> b;
};
    

int main() {

    B b{};

    if (b.a.a || b.a.b || b.a.c || b.a.d) throw std::runtime_error("Compiler bug?");
}

If I understand correctly, according to https://en.cppreference.com/w/cpp/language/zero_initialization, it cannot throw because zero initialization should be performed for B::a as it should for "members of value-initialized class types that have no constructors".

If it throws, is it a compiler bug or am I missing something?

[edit]

Here with clang 10 and optimization enabled it just does "mov eax, 2" and "ret" (meaning the condition is false): https://godbolt.org/z/CXrc3G

But if I remove the braces, it does "mov eax, 1" and "ret" (meaning the condition is true). But here I think it can return whatever it want because it's just UB. https://godbolt.org/z/tBvLzZ

So it seems clang thinks that with braces zero initialization must be performed.

Edit: I submitted a bug on intel's website: https://community.intel.com/t5/Intel-C-Compiler/Aggregate-initialization-bug-with-nested-struct/td-p/1178228

An intel person replied "I've reported this issue to our Developer." Poor developer, single-handedly supporting all icc development.

ThreeStarProgrammer57
  • 2,906
  • 2
  • 16
  • 24
  • Actually, it works at cpp.sh: http://cpp.sh/8g46t Not sure which compiler though... –  May 05 '20 at 00:45
  • 1
    That cppreference page is not very good; firstly the cases under Syntax(2) are *value-initialization* which only sometimes translates to zero-initialization; and secondly in the Explanation(2) it talks about "class types that have no constructors", however every class has at least one constructor (which is implicitly defined if not declared by the user, and may be defined as deleted) – M.M May 05 '20 at 00:45
  • @Hydroper your example does not prove whether the code is guaranteed to produce that result, or it could do various things but happens to give that result on your system – M.M May 05 '20 at 00:46
  • @Hydroper yeah, I have this exception thrown when using icc 17.4 on my machine, but I can't reproduce it on compiler explorer with any available version of icc. – ThreeStarProgrammer57 May 05 '20 at 00:48
  • @M.M Usually I trust cppreference, but here I guess I should look directly in the c++14 standard. What part of the standard would be relevant here? – ThreeStarProgrammer57 May 05 '20 at 00:50
  • 2
    Mostly [dcl.init] 8.5 Initializers. It has a lot of cross references into and between its subsections, making a bit of a maze. I believe in this case you actually have aggregate initialization, and not zero-initialization or class value-initialization, but that should still initialize the members to zero. – aschepler May 05 '20 at 00:54

1 Answers1

4

Firstly: the objects b.a.a, b.a.b, b.a.c, b.a.d are guaranteed to be zero-initialized. Which for float is initialized as if by = 0; (not necessarily a representation of all bits zero).

B b{}; only translates to zero-initialization in some cases (the cppreference page is a bit misleading).

In C++14: Since B is an aggregate , this is aggregate initialization, each member is initialized as if by an empty list . So A a; gets initialized as if A a{};. The A is also an aggregate so each of its elements is initialized as if by an empty list, which for built-in types is zero-initialization.

In C++11 the wording was different (list initialization of an aggregate class from an empty list was not actually considered aggregate initialization) but the outcome was the same.

In C++03, B b{}; was a syntax error, but B b = {}; was allowed, and also turns out to zero-initialize the floats in question.

In C++98, the rules were different and to cut a long story short, B b = {}; would call the default constructor of A which leaves the values uninitialized. We like to pretend C++98 initialization never existed, but some compilers clung onto those rules even into the 2010s.


With that out of the way, there might be some argument about whether a zero-initialized float is guaranteed to act as false for the || operator, see Comparing floating point number to zero.

The standard says "A zero value, null pointer value, or null member pointer value is converted to false". Which isn't 100% precise but IMO the zero-initialized float should count as "a zero value" for this purpose.

M.M
  • 138,810
  • 21
  • 208
  • 365
  • Thanks for your very precise answer. Initially I wasn't testing these values with boolean context, I came across this behavior because of unexpected uninitialized float values. So you are saying this is indeed an icc bug? Or should I reproduce with integers to be 100% sure? – ThreeStarProgrammer57 May 05 '20 at 01:10
  • 1
    @ThreeStarProgrammer57 reproducing with integers would be simpler than floats, and maximum simplification is always best for reporting compiler bugs. I updated my answer to include pre-c++11 cases too – M.M May 05 '20 at 01:14
  • @ThreeStarProgrammer57 also check that the compiler is invoked in C++11 or later mode – M.M May 05 '20 at 01:17
  • 1
    Yes it's compiled with -std=c++14. Also we have generic lambdas with auto parameters. I just reproduced with `long long int`s, so I'll try to submit a bug report to intel. – ThreeStarProgrammer57 May 05 '20 at 01:25
  • I don't agree that `B b{};` is a value-initialization when `B` is an aggregate type. I also don't see a reason C++11 wouldn't consider this aggregate initialization, at least looking at [dcl.init]. – aschepler May 05 '20 at 01:31
  • @aschepler fixed the first point. For the second, C++11 [dcl.init.list]/3 first bullet point applies (`B` has a default constructor, albeit implicitly defined) – M.M May 05 '20 at 01:35
  • Oh, right. I was looking at the wrong version for what I thought was "C++11". – aschepler May 05 '20 at 01:52