Why are many curly brackets treated differently by C++ compilers?
Asked Answered
C

1

6

In the following C++20 program I put by mistake one extra pair of curved braces {} in B{{{{A{}}}}}:

#include <iostream>

struct A
{
    A() { std::cout << "A() "; }
    A( A&& ) = delete;
    ~A() { std::cout << "~A() "; }
};

struct B { std::initializer_list<A> l; };

int main()
{
    [[maybe_unused]] auto x = B{{{{A{}}}}};
    std::cout << ". ";
}

Clang rejected it, however with a strange error:

error: call to deleted constructor of 'const A'

But to my surprise GCC accepted it( https://gcc.godbolt.org/z/aPWe13xfc ).

Could you please explain why GCC accepts it (how does it treat extra curved braces)?

Covington answered 18/7, 2021 at 9:32 Comment(3)
Obligatory The Nightmare of Initialization in C++ by Nicolai Josuttis. An hour long presentation on the initialization syntax pain point in C++, including this specific issue (although either Clang or GCC probably has a standard compliance bug — but which one? hard to say, may need a language-lawyer).Perea
I am so fascinated with the operator {{{{.}}}} that believe it deserves a proper name.Incorporate
@TioTeo how about "rosette operator"?Stepaniestepbrother
C
4

B{…}, since the single element of the initializer list is not designated and is not of type B (as it has no type at all), is aggregate initialization ([dcl.init.list]/3.4). B::l is thus copy-initialized from {{{A{}}}}; it's a specialization of std::initializer_list, so /3.6 and /5 apply. An "array of 1 const A" is created, and {{A{}}} is the initializer for its single element.

We can thus reduce the code to

const A a = {{A{}}};

with no mention of B at all, and indeed Clang and GCC produce the same disagreement for this line. Clang appears to be correct to reject it: that initialization is sent to constructors by /3.7, and obviously there is no constructor that could be viable (thus the error about the deleted move constructor).

Oddly, removing the extra pair of braces here (or in the original) causes both compilers to accept:

const A a = {A{}};

despite the fact that A is not an aggregate and so /3.7 still applies. Presumably both compilers are overenthusastically performing "guaranteed copy elision" (albeit to different degrees), identifying the prvalue A{} with an object eventually to be initialized by it; that, however, takes place only per [dcl.init.general]/16.6.1, which never comes into play in this analysis.

Celebrity answered 18/7, 2021 at 21:0 Comment(3)
what is "dcl.init.general"? That doesn't appear in N4860 (final draft of C++20) or C++17Venusian
@M.M: That was the DIS; ISO required that more subclause headings be introduced (most called [*.general]) prior to publication to avoid paragraphs with subclauses as siblings. N4868 has them, along with a few purely editorial changes after C++20.Celebrity
I reported gcc bug: gcc.gnu.org/bugzilla/show_bug.cgi?id=101500, but they disagree saying that A a = {{A{}}}; is the same as A a = A(A(A())); and referring open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#2311Covington

© 2022 - 2024 — McMap. All rights reserved.