After doing some research in the standard I came to the conclusion that g++ is wrong and there should be only one copy constructor invocation. What is interesting it seems that there can be two interpretations of which type of initialization occurs here. Both lead to the same conclusion though.
First interpretation - direct initialization
From the C++14 Standard (Working Draft), [expr.new] 17:
A new-expression that creates an object of type T
initializes that object as follows:
- (17.1) — If the new-initializer is omitted, the object is default-initialized (8.5). [ Note: If no initialization is
performed, the object has an indeterminate value. — end note ]
- (17.2) — Otherwise, the new-initializer is interpreted according to the initialization rules of 8.5 for direct initialization.
In our case the new-initializer is present, so (according to 17.2) new A[1]{x}
is interpreted using direct initialization rules. Let's look at [dcl.init] 16:
The initialization that occurs in the forms
as well as in new
expressions (5.3.4), static_cast
expressions (5.2.9), functional notation type conversions
(5.2.3), mem-initializers (12.6.2), and the braced-init-list form of a condition is called direct-initialization
Ok, this further confirms that we are dealing with direct initialization. Now let's see how direct initialization works in [dcl.init] 17:
The semantics of initializers are as follows. The destination type is the type of the object or reference being
initialized and the source type is the type of the initializer expression. If the initializer is not a single (possibly
parenthesized) expression, the source type is not defined.
- [... 17.1 through 17.5 omitted ...]
- (17.6) — If the destination type is a (possibly cv-qualified) class type:
- (17.6.1) — If the initialization is direct-initialization, or if it is copy-initialization where the cv-unqualified
version of the source type is the same class as, or a derived class of, the class of the destination,
constructors are considered. The applicable constructors are enumerated (13.3.1.3), and the best
one is chosen through overload resolution (13.3). The constructor so selected is called to initialize
the object, with the initializer expression or expression-list as its argument(s). If no constructor
applies, or the overload resolution is ambiguous, the initialization is ill-formed.
According to the excerpt above, when the object being initialized is a class type (as is the case here) and when dealing with direct initialization (as is the case here) the destination object is initialized using the most suitable constructor.
I won't cite the rules about how the constructor is selected, as in this case when there is only the default A::A()
constructor and the copy A::A(const A&)
constructor, the copy constructor is obviously the better choice when initializing with x
of type A
. This is the source of one of the copy constructor invocations.
I didn't find any remarks about the initialization of arrays in particular in section [expr.new] and why it should cause a second constructor invocation.
Second interpretation - copy initialization
Here, we can start from [dcl.init.list] 1:
List-initialization is initialization of an object or reference from a braced-init-list. Such an initializer is
called an initializer list, and the comma-separated initializer-clauses of the list are called the elements of the
initializer list. An initializer list may be empty. List-initialization can occur in direct-initialization or copy initialization contexts; list-initialization in a direct-initialization context is called direct-list-initialization and
list-initialization in a copy-initialization context is called copy-list-initialization. [ Note: List-initialization
can be used
- (1.1) — as the initializer in a variable definition (8.5)
- (1.2) — as the initializer in a new-expression (5.3.4)
- [... 1.3 through 1.10 omitted ...]
— end note ]
This excerpt can be understood to say that new A[1]{x}
is actually a form of list intialization rather than direct initialization as a braced-init-list {x}
is used. Assuming this is the case, let's look at how it works in [dcl.init.list] 3:
List-initialization of an object or reference of type T
is defined as follows:
- [... 3.1 through 3.2 omitted ...]
- (3.3) — Otherwise, if
T
is an aggregate, aggregate initialization is performed (8.5.1).
- [... 3.4 through 3.10 omitted ...]
In our case, point 3.3 applies as we are initializing an array which is an aggregate, according to [dcl.init.aggr] 1:
An aggregate is an array or a class (Clause 9) with no user-provided constructors (12.1), no private or
protected non-static data members (Clause 11), no base classes (Clause 10), and no virtual functions (10.3).
As such let's look at how aggregate initialization is performed in [dcl.init.aggr] 2:
When an aggregate is initialized by an initializer list, as specified in 8.5.4, the elements of the initializer list
are taken as initializers for the members of the aggregate, in increasing subscript or member order. Each
member is copy-initialized from the corresponding initializer-clause. If the initializer-clause is an expression
and a narrowing conversion (8.5.4) is required to convert the expression, the program is ill-formed.
This fragment tells us that elements are copy initialized. As such y[0]
will be copy initialized from x
. Now let's look at how copy initialization works in [dcl.init] 17:
The semantics of initializers are as follows. The destination type is the type of the object or reference being
initialized and the source type is the type of the initializer expression. If the initializer is not a single (possibly
parenthesized) expression, the source type is not defined.
- [... 17.1 through 17.5 omitted ...]
- (17.6) — If the destination type is a (possibly cv-qualified) class type:
- (17.6.1) — If the initialization is direct-initialization, or if it is copy-initialization where the cv-unqualified
version of the source type is the same class as, or a derived class of, the class of the destination,
constructors are considered. The applicable constructors are enumerated (13.3.1.3), and the best
one is chosen through overload resolution (13.3). The constructor so selected is called to initialize
the object, with the initializer expression or expression-list as its argument(s). If no constructor
applies, or the overload resolution is ambiguous, the initialization is ill-formed.
Just like last time, this initialization fulfills the requirements for point 17.6.1 as it is copy-initialization where the source type (A
of x
) is the same as the destination type (A
of y[0]
). This means that in this case the copy constructor will be called as well.
Conclusion
It seems that regardless of which interpretation is chosen, only one constructor should be called and that Clang is right. I was unable to find any evidence that a temporary should be created. For some more example-based evidence, other compilers like icc
, and (admittedly clang-based) zapcc
and elcc
agree with clang, all having only one copy constructor invocation.
I don't know much about g++
's internal workings, but I have a theory about why it does two copy constructor invocations. It is possible that internally g++
uses some helper constructor invocations that are later always optimized out and that the use of the -fno-elide-constructors
flag breaks the invariance that they will be always optimized out. This is however pure speculation about g++
on my side, so please correct me if I'm wrong.
-std=c++17
: godbolt.org/z/538fqd9KM – Marlow