I have the following code snippet:
#include <vector>
struct X {
std::vector<X> v;
X(std::vector<X> vec) : v{vec} {}
};
int main() {
X{{}};
}
Compiling and running this snippet locally results in a stack overflow originating from X's constructor. Inspection with gdb shows me that the constructor is somehow recursively calling itself, but that doesn't make sense because I'm not calling it recursively. Why might this be happening and what can I do to fix it?
The problem has to do with how C++ chooses which constructor to execute. Notice that X's constructor uses curly-brace syntax in the member initialization list, meaning that it's using list-initialization syntax. According to the C++ reference:
When an object of non-aggregate class type T is list-initialized, two-phase overload resolution takes place.
at phase 1, the candidate functions are all initializer-list constructors of T and the argument list for the purpose of overload resolution consists of a single initializer list argument
if overload resolution fails at phase 1, phase 2 is entered, where the candidate functions are all constructors of T and the argument list for the purpose of overload resolution consists of the individual elements of the initializer list.
If the initializer list is empty and T has a default constructor, phase 1 is skipped.
In copy-list-initialization, if phase 2 selects an explicit constructor, the initialization is ill-formed (as opposed to all over copy-initializations where explicit constructors are not even considered).
std::vector has an initializer-list constructor as noted here, so this constructor is prioritized in the aforementioned phase 1. In the question, the non-explicit constructor for X means that the provided vector vec can be implicitly converted to an X, and so the vector constructor can be applied. Since this implicit conversion calls X's constructor, you end up with the recursion described above.
To fix this, either mark X's constructor as explicit, or switch to v(vec) syntax in the constructor.
Related
I learnt about the explicit keyword in c++ and its uses. Then for practice I wrote the following program that works with MSVC but not with gcc and clang. Demo.
class Testing
{
public:
explicit Testing() = default;
};
int main()
{
Testing t[2] = {}; //works with msvc but not with gcc and clang
}
As you can see, the above works with msvc but not with clang and gcc. I am using C++20 and want to know which compiler is correct for this example according to the C++20 standard.
GCC produces the following error:
<source>:8:21: error: converting to 'Testing' from initializer list would use explicit constructor 'constexpr Testing::Testing()'
8 | Testing t[2] = {}; //works with msvc but not with gcc and clang
| ^
The program is ill-formed and gcc and clang are correct in rejecting the program because Testing t[2]={}; is copy-list-initialization and since t is an array it uses aggregate initialization which in turn result in copy initialization of t's member(s) using the empty initializer list {} which fails as the default ctor is explicit(and so Testing::Testing() cannot be used in copy initialization).
This is explained in detail below.
From list initialization:
3) List-initialization of an object or reference of type T is defined as follows:
3..4) Otherwise, if T is an aggregate, aggregate initialization is performed
This means that since T in our example is Testing[2] which is an array(and so an aggregate), aggregate initialization will be performed.
Next, from aggregate initialization:
5) For a non-union aggregate, each element that is not an explicitly initialized element is initialized as follows:
5.4) Otherwise, if the element is not a reference, the element is copy-initialized from an empty initializer list.
This means that the array elements(which are not explicitly initialized btw) will be copy initialized from an empty initializer list {}. What this in turn means is that it is as if for each element of the array we're writing Testing array_element_nth = {}; which results in value-initialization as per dcl.init.list#3.5:
Otherwise, if the initializer list has no elements and T is a class type with a default constructor, the object is value-initialized.
Next, from value initialization:
To value-initialize an object of type T means:
if T has either no default constructor ([class.default.ctor]) or a default constructor that is user-provided or deleted, then the object is default-initialized;
This means that the array element will be default initialized.
So we move onto default initialization:
To default-initialize an object of type T means:
If T is a (possibly cv-qualified) class type ([class]), constructors are considered.
The applicable constructors are enumerated ([over.match.ctor]), and the best one for the initializer () is chosen through overload resolution ([over.match]).
The constructor thus selected is called, with an empty argument list, to initialize the object.
This means that that the constructors will be enumerated and best of them will be chosen with (). So we move onto over.match.ctor:
When objects of class type are direct-initialized, copy-initialized from an expression of the same or a derived class type ([dcl.init]), **or default-initialized, overload resolution selects the constructor.
For direct-initialization or default-initialization that is not in the context of copy-initialization, the candidate functions are all the constructors of the class of the object being initialized.
For copy-initialization (including default initialization in the context of copy-initialization), the candidate functions are all the converting constructors ([class.conv.ctor]) of that class.
The argument list is the expression-list or assignment-expression of the initializer.
This means that the default ctor will be used but since this is in copy initialization context, the explicit ctor cannot be used.
Thus, msvc is wrong in accepting the program.
Here is the msvc bug:
MSVC compiles invalid program involving explicit constructor
#include <iostream>
#include <vector>
int main()
{
auto v1 = std::vector<std::size_t>(std::size_t{8});
std::cout << v1.size() << std::endl;
auto v2 = std::vector<std::size_t>{std::size_t{8}};
std::cout << v2.size() << std::endl;
}
The code outputs:
8
1
I know this is a well-known problem in C++ because of:
std::vector<std::size_t>(std::size_t{8}) calls
explicit vector(size_type count) while
std::vector<std::size_t>{std::size_t{8}} calls
vector(std::initializer_list<T> init, const Allocator& alloc = Allocator()).
To my surprise:
Why does the second call not trigger a compile-time error for overload resolution ambiguity?
In another related question, a piece of similar code does trigger an ambiguity error.
Because there is no ambiguity that can cause an error. Overload resolution is explicitly different when we use list initialization. It's done intentionally in two phases.
[over.match.list]
1 When objects of non-aggregate class type T are list-initialized
([dcl.init.list]), overload resolution selects the constructor in two
phases:
Initially, the candidate functions are the initializer-list constructors ([dcl.init.list]) of the class T and the argument list
consists of the initializer list as a single argument.
If no viable initializer-list constructor is found, overload resolution is performed again, where the candidate functions are all
the constructors of the class T and the argument list consists of the
elements of the initializer list.
If the initializer list has no elements and T has a default
constructor, the first phase is omitted. In copy-list-initialization,
if an explicit constructor is chosen, the initialization is
ill-formed. [ Note: This differs from other situations
([over.match.ctor], [over.match.copy]), where only converting
constructors are considered for copy-initialization. This restriction
only applies if this initialization is part of the final result of
overload resolution. — end note ]
In the first step only std::initializer_list constructors are considered. And we can reach the second step where other constructors are considered only if the first overload resolution failed. Obviously overload resolution does not fail to find an appropriate std::initializer_list constructor.
The question you link to is not about ambiguity in initializing vectors. The ambiguity is in choosing a function overload. Both are viable because both accept a different vector that can be itself initialized unambiguously from the same initializer list.
Based on this code
struct Foo
{
Foo()
{
cout << "default ctor" << endl;
}
Foo(std::initializer_list<Foo> ilist)
{
cout << "initializer list" << endl;
}
Foo(const Foo& copy)
{
cout << "copy ctor" << endl;
}
};
int main()
{
Foo a;
Foo b(a);
// This calls the copy constructor again!
//Shouldn't this call the initializer_list constructor?
Foo c{b};
_getch();
return 0;
}
The output is:
default ctor
copy ctor
copy ctor
In the third case, I'm putting b into the brace-initialization which should call the initializer_list<> constructor.
Instead, the copy constructor takes the lead.
Will someone of you tell me how this works and why?
As pointed out by Nicol Bolas, the original version of this answer was incorrect: cppreference at the time of writing incorrectly documented the order in which constructors were considered in list-initialization. Below is an answer using the rules as they exist in the n4140 draft of the standard, which is very close to the official C++14 standard.
The text of the original answer is still included, for the record.
Updated Answer
Per NathanOliver's comment, gcc and clang produce different outputs in this situation:
g++ -std=c++14 -Wall -pedantic -pthread main.cpp && ./a.out
default ctor
copy ctor
copy ctor
initializer list
clang++ -std=c++14 -Wall -pedantic -pthread main.cpp && ./a.out
default ctor
copy ctor
copy ctor
gcc is correct.
n4140 [dcl.init.list]/1
List-initialization is initialization of an object or reference from a braced-init-list.
You're using list-initialization there, and since c is an object, the rules for its list-initialization are defined in [dcl.init.list]/3:
[dcl.init.list]/3:
List-initialization of an object or reference of type T is defined as follows:
If T is an aggregate...
Otherwise, if the initializer list has no elements...
Otherwise, if T is a specialization of std::initializer_list<E>...
going through the list so far:
Foo is not an aggregate.
It has one element.
Foo is not a specialization of std::initializer_list<E>.
Then we hit [dcl.init.list]/3.4:
Otherwise, if T is a class type, constructors are considered. The applicable constructors are enumerated and the best one is chosen through overload resolution (13.3, 13.3.1.7). If a narrowing conversion (see below) is required to convert any of the arguments, the program is ill-formed.
Now we're getting somewhere. 13.3.1.7 is also known as [over.match.list]:
Initialization by list-initialization
When objects of non-aggregate class type T are list-initialized (8.5.4), overload resolution selects the constructor in two phases:
Initially, the candidate functions are the initializer-list constructors (8.5.4) of the class T and the argument list consists of the initializer list as a single argument.
If no viable initializer-list constructor is found, overload resolution is performed again, where the candidate functions are all the constructors of the class T and the argument list consists of the elements of the initializer list.
So the copy constructor will only be considered after the initializer list constructors, in the second phase of overload resolution. The initializer list constructor should be used here.
It's worth noting that [over.match.list] then continues with:
If the initializer list has no elements and T has a default constructor, the first phase is omitted. In copy-list initialization, if an explicit constructor is chosen, the initialization is ill-formed.
and that after [dcl.init.list]/3.5 deals with single-element list initialization:
Otherwise, if the initializer list has a single element of type E and either T is not a reference type or its referenced type is reference-related to E, the object or reference is initialized from that element; if a narrowing conversion (see below) is required to convert the element to T, the program is ill-formed.
which explains where cppreference got their special case for single-element list initialization, though they placed it higher in the order than it should be.
Original Answer
You're encountering an interesting aspect of list initialization, where if the list fulfills certain requirements it may be treated like a copy-initialization rather than a list-initialization.
from cppreference:
The effects of list initialization of an object of type T are:
If T is a class type and the initializer list has a single element of
the same or derived type (possibly cv-qualified), the object is
initialized from that element (by copy-initialization for
copy-list-initialization, or by direct-initialization for
direct-list-initialization). (since c++14)
Foo c{b} fulfills all these requirements.
Let us examine what the C++14 specification says about list initialization here. [dcl.init.list]3 has a sequence of rules which are to be applied in order:
3.1 does not apply, since Foo is not an aggregate.
3.2 does not apply, since the list is not empty.
3.3 does not apply, since Foo is not a specialization of initializer_list.
3.4 does apply, since Foo is a class type. It says to consider constructors with overload resolution, in accord with [over.match.list]. And that rule says to check initializer_list constructors first. Since your type has an initilaizer_list constructor, the compiler must check to see if an initializer_list matching one of those constructors can be manufactured from the given values. It can, so that is what must be called.
In short, GCC is right and Clang is wrong.
It should be noted that the C++17 working draft changes nothing about this. It has a new section 3.1 that has special wording for single-value lists, but that only applies to aggregates. Foo is not an aggregate, so it does not apply.
I recently realized that in C++11 we can call a delegating initializer-list constructor like
Foo() : Foo{42} // delegate to Foo(initializer_list<>)
Is this syntax correct? It seems to be, although I would have expected to always use parentheses when calling a function, like Foo({42}). The example code below compiles fine in both clang++ and g++
#include <iostream>
#include <initializer_list>
struct Foo
{
Foo() : Foo{42} // I would have expected invalid syntax, use Foo({42})
{
std::cout << "Foo()... delegating constructor\n";
}
Foo(std::initializer_list<int>)
{
std::cout << "Foo(initializer_list)\n";
}
};
int main()
{
Foo foo;
}
I am well aware of uniform initialization, like declaring objects using { }, but did not know we can also call constructors. We cannot call functions though, the following doesn't compile:
#include <initializer_list>
void f(std::initializer_list<int>){}
int main()
{
f{5}; // compile time error, must use f({5})
}
So, to summarize, my question is the following: are there special rules when delegating constructors, that allow for calling a init-list constructor using only braces, like Foo{something}?
Yes, a mem-initializer such as Foo{42} can contain either a parenthesized expression-list or a braced-init-list. This is the case regardless of whether the mem-initializer-id denotes the constructor's class, a base class, or a member: that is, both when the constructor delegates and when it does not. See the grammar in [class.base.init].
Furthermore, the standard specifies ([class.base.init]/7 in C++14) that the initialization by the expression-list or braced-init-list occurs according to the usual rules of initialization. Therefore if the initializer is a braced-init-list then std::initializer_list constructors will be favoured in overload resolution.
I think the rule is pretty clear that you will be allowed to delegate to an initializer list constructor (emphasis mine):
If the name of the class itself appears as class-or-identifier in the
member initializer list, then the list must consist of that one member
initializer only; such constructor is known as the delegating
constructor, and the constructor selected by the only member of the
initializer list is the target constructor In this case, the target
constructor is selected by overload resolution and executed first,
then the control returns to the delegating constructor and its body is
executed.
So by overload resolution, you can call your initializer list constructor just as if you were calling it in 'normal' code because.
However, I don't know of anything that should allow calling a function that accepts an initializer list in the same way that you can call a constructor with one.
Edit: More about constructor rules (Emphasis again mine):
The body of a function definition of any constructor, before the
opening brace of the compound statement, may include the member
initializer list, whose syntax is the colon character :, followed by
the comma-separated list of one or more member-initializers, each of
which has the following syntax
class-or-identifier (expression-list(optional) ) (1)
class-or-identifier brace-init-list (2) (since C++11)
parameter-pack ... (3) (since C++11)
1) Initializes the base or member named by class-or-identifier using
direct initialization or, if expression-list is empty,
value-initialization
2) Initializes the base or member named by
class-or-identifier using list-initialization (which becomes
value-initialization if the list is empty and aggregate-initialization
when initializing an aggregate)
3) Initializes multiple bases using a
pack expansion
So according to #2, it appears it's legal.
In this code fragment, which constructor is actually called?
Vector v = getVector();
Vector has copy constructor, default constructor and assignment operator:
class Vector {
public:
...
Vector();
Vector(const Vector& other);
Vector& operator=(const Vector& other);
};
getVector returns by value.
Vector getVector();
Code uses C++03 standard.
Code fragment looks like it is supposed to call default constructor and then assignment operator, but I suspect that this declaration is another form of using copy constructor. Which is correct?
When = appears in an initialization, it calls the copy constructor. The general form is not exactly the same as calling the copy constructor directly though. In the statement T a = expr;, what happens is that if expr is of type T, the copy constructor is called. If expr is not of type T, then first an implicit conversion is done, if possible, then the copy constructor is called with that as an argument. If an implicit conversion is not possible, then the code is ill-formed.
Depending upon how getVector() is structured, the copy may be optimized away, and the object that was created inside the function is the same physical object that gets stored in v.
Assuming that you haven't done something pathological outside of the code you are showing, your declaration is a copy-initialization, and the second part of this rule applies:
13.3.1.3 Initialization by constructor [over.match.ctor]
1 When objects of class type are direct-initialized (8.5), or copy-initialized from an
expression of the same or a derived class type (8.5), overload resolution selects the
constructor. For direct-initialization, the candidate functions are all the constructors
of the class of the object being initialized. For copy-initialization, the candidate
functions are all the converting constructors (12.3.1) of that class. The argument
list is the expression-list within the parentheses of the initializer.
For a simple test case, see Eli Bendersky's post, here: http://eli.thegreenplace.net/2003/07/23/variable-initialization-in-c/
Always remember the rule:
Whenever, an object is being created and given some value in the same single statement then it is never an assignment.
To add Further,
Case 1:
Vector v1;
Vector v(v1);
Case 2:
Vector v = getVector();
In the above two formats Case 1 is Direct Initialization while Case 2 is known as Copy Initialization.
How does Copy Initialization work?
Copy initialization constructs an implicit conversion sequence: It tries to convert return value of getVector() to an object of type Vector. It can then copy the created object into the object being initialized, So it needs a accessible copy constructor.
The copy constructor actually gets elided in this case (check this) and just the default constructor ends up getting called
EDIT:
The constructor is only sometimes elided, as per Benjamin's answer. For some reason I read that as you calling the constructor directly.