C++: function call expression with braced-init-list - does standard prescribe to ignore braces in a trivial case of a single element list?

Viewed 129

Consider the following example:

class X;

void f(const X &);

void g()
{
    X x;
    f({x});
}

Does standard require that an implementation ignored curly braces in that case? Without any optimization involved. If yes, than since which version?

On a first glance, it looks like by rules there should be a temporary created - completely unnecessary, of course, but still. Looking at list initialization I cannot find anything relevant. X is not an aggregate here.

Both GCC and Clang, even with -O0, produce code without temporary created - even if an X copy constructor has observable side-effects and even if X has X(std::initializer_list<X>) constructor.

1 Answers

The initialization of X const &x (the argument of f) in f({x}) is list initialization in a copy initialization context, by [dcl.init]/15. Thus, we can drop the function and just ask what this means:

int main() {
    X x;
    X const &y = {x}; // still copy list initialization
}

Now, the first clause in [dcl.init.list] to apply to this is [dcl.init.list]/3.9, which states that you basically just drop the braces.

... if the initializer list has a single element of type E and either T [here X const&] is not a reference type or its referenced type is reference-related to E, the object or reference is initialized from that element (by copy-initialization for copy-list-initialization, or by direct-initialization for direct-list-initialization); ....

X const& is in fact a reference type, but its referenced type X const is indeed related to the initializer's type, X, so the clause still applies. Now we just have

int main() {
    X x;
    X const &y = x; // (non-list/"plain") copy-initialization
}

and of course that doesn't call X's constructor (by [dcl.init.ref]/5.1).

Note that the above quote appears slightly reworded on your cppreference page, too:

... (if T is not a class type), if the braced-init-list has only one element and either T isn't a reference type or is a reference type whose referenced type is same as or is a base class of the type of the element, T is direct-initialized (in direct-list-initialization) or copy-initialized (in copy-list-initialization), ....

Perhaps the "T isn't a reference type" or "T is not a class type" made this fly under the radar, but this is the clause you were looking for, since a) a reference type is indeed not a class type and b) the "or ..." in the preconditions makes it apply. Judging by the lack of versioning boxes, this behavior would be as old as list initialization itself: since C++11.

Related