List initialization, Initializer_list and related questions in C++ - c++

I know it's a subject that has been discussed quite extensively here on stackoverflow but I've a hard time finding thorough answers removing all the confusion I've concerning list initialization and initializer_lists in C++, so I will just give it a try and ask my own questions.
Please, consider the following snippet of code :
class C
{
public :
C(int a, int b, int c) : _a (a), _b(b), _c(c) {}; //initialization_list with ()
//C(int a, int b, int c) : _a{ a }, _b{ b }, _c{ c } {}; //initialization list with {}
private :
int _a, _b, _c;
};
int main()
{
C a(5.3,3.3,4.3); // no list
C b{5.3,3.3,4.3}; // list {}
C c({5.3,3.3,4.3}); // list {}
}
I don't understand why those two initialization lists behave similarly? I was expecting, when trying to create the object a of type C using the initialization list of type _a{a}, _b{b}, _c{c} to get a compiler error about narrowing. However, no errors are generated and _a, _b and _c just store the integer values.
It's only when creating the objects b or c using a list "{}" that the compiler generates a narrowing error message. Why is that? Is there any differences between writing an initialization list using {} or () that I'm unaware of or are the behaviours identical?
Come my next question:
class C
{
public :
//private :
int _a, _b, _c;
};
int main()
{
C a(5,3,4); //obviously doesn't work as no such constructor
C b{5,3,4}; //work only if _a, _b and _c are not private nor protected!
}
How comes that the second statement (with braces) only works if the variables are public? What is the mechanism involved?
So I would like to better understand, beside the "narrowing safety" provided by creating an object with a list {}, what other "functionalities" does this list mechanism provide ? Because in the second call, it's still the default constructor that is called (hence, not a default constructor taking an initializer_list as argument), right ?
Lastly, imagine in my class C I've another constructor taking an initializer list as parameter.
class C
{
public :
C() = default; //default constructor
C(int a, int b, int c) : _a (a), _b(b), _c(c) {};
C(std::initializer_list<int> a) { //do some stuffs with the list};
private :
int _a, _b, _c;
};
It's pretty obvious that, if trying to create an object taking any numbers of integers but 3 (or 0 actually), the constructor taking the initializer_list will be invoked. If creating an object like that however :
C c();
or
C c{};
the default constructor will be called. But if creating an object with exactly 3 integers :
C c(5,2,3);
or
C c{5,2,3};
the initializer_list constructor will be called. The rule goes like that :
If either a default constructor or an initializer-list constructor could be invoked, prefer the default constructor
If both an initializer-list contructor and an "ordinary constructor" could be invoked, prefer the initializer-list constructor
Therefore (and correct me if I'm wrong), if I create my object like that :
C c{5,3,4};
The iniatializer-list constructor will be called. However, if I create my object like that :
C c(5,3,4);
The second constructor (taking 3 ints as arguments) will be called. My question is : how can I create an object with this second constructor instead of the iniatializer-list one if I want to provide narrowing safety as well ? (because if I do as in the first example of this question, the initializer-list constructor will be called instead !).
Don't hesitate to examplify your replies and to discuss on list-related concepts I haven't talk in this question. I would like to get a very good grasp on those. Thanks.

So anytime curly braces are used you are using aggregate initialization, a method of initialization for structs or classes that initializes in order., or via a designator. For example,
#include <iostream>
struct Foo
{
int a;
char b;
};
class Doo
{
public:
double h;
char ch;
};
int main() {
Foo first = {3, 't'};
std::cout << first.b << "\n";
//t
Doo second = {'3', 50};
std::cout << second.ch << "\n";
//2 interpreted as char
}
Here, when we use the {} to initialize a class or struct, they are always interpreted as being in the order listed in the class. That's why '2' was printed since 50 in ASCII corresponds to the character '2'.
Constructor Initialization
So you can also use the same logic with constructor initialization lists,
#include <iostream>
struct Pair
{
int first;
long second;
};
class Couple
{
public:
Pair p;
int g;
public:
Couple(): p{3, 700}, g{3}
{}
};
int main() {
Couple test;
std::cout << test.p.first << "\n";
//3
}
Here, the {3, 700} next to p, would be the same as Pair p = {3, 700}; used else where in code. Your basically using an in order aggregate initialization. Now, what happens if we change the curly braces for the Pair field to parenthesis?
We get this error
main.cpp: In constructor 'Couple::Couple()':
main.cpp:15:26: error: no matching function for call to 'Pair::Pair(int, int)'
Couple(): p(3, 700), g{3}
That's because we don't have a constructor for Pair that accepts two numbers. So the key difference between the aggregate initialization and the parenthesis is you need to have constructors implemented for any specific set of arguments you make with parenthesis, yet with curly braces you can just use the default one the compiler hands you.
The std::initializer_list is a not-commonly used form of container for multiple arguments in initialization lists with {}.

Related

How is the implementation of initializer_list changes the way the compiler reads?

I've just learned that we can use the standard library type initializer_list<T> to achieve initializing a class instance with a {}-delimited list of elements of type T. E.g.
class X
{
public:
X(initializer_list<int> lst)
{
cout << "initializer-list constructor\n";
}
X(int i, int j, int k)
{
cout << "constructor taking 3 ints\n";
}
};
int main()
{
X a{ 1,2,3 }; // initializer-list constructor
}
To my knowledge, when the compiler sees { 1,2,3 } in the above code, it will first seek for a constructor that takes a initializer_list<int>, and since there is one, it will construct a initializer_list<int> object out of { 1,2,3 }. Had there been no constructor which takes a initializer_list<int>, the compiler will generate code the same as for X a(1,2,3).
If the process I described above is correct, to me there is some "magic" going on here: I don't understand why defining a type (initializer_list) and let a function (the initializer-list constructor) accept an object of that type (say, lst as above) can "change" the way the function is called. By that I mean: the compiler can now understand {} notation so that we need not to have things like X a({1,2,3}), and in such cases prefers the initializer-list constructor to other constructors while treating {} notation as () notation when the initializer-list constructor is absent.
More to the point, can I define a type (say, my own initializer_list) such that when the compiler sees a special form of instantiation of a class (say, a {} initializer list), it will search for a constructor in that class that takes an object of my own initializer_list type, and will treat that special form of instantiation in some other way if such constructor does not exist?
In other words:
class X
{
public:
X(initializer_list<int> lst)
{
cout << "std\n";
}
X(my_initializer_list<int> lst)
{
cout << "user-defined\n";
}
};
int main()
{
X b{ 1,2,3 };
}
Can I implement a my_initializer_list in the global scope so that the X b{ 1,2,3 }; calls X::X(my_initializer_list<int> lst)?
My current guess is, it is a feature of the language itself that, when the compiler sees a {} initializer list, the compiler will always first seek for the constructor that takes a std::initializer_list, rather than a initializer_list defined in any other namespace (by that I mean, such behavior of the compiler is not implemented in the std::initializer_list, but is a feature designed into the compiler in the first place). May I ask if my guess is on the right track?
There is no magic. This is a type of list of initialization, the rules for which are available here: https://en.cppreference.com/w/cpp/language/list_initialization.
Can I implement a my_initializer_list in the global scope so that the X b{ 1,2,3 }; calls X::X(my_initializer_list lst)?
No, this won't do it. Read the above rules - there's a special handling for initializer-list. If there isn't a constructor taking in an intializer-list, the constructor which matches the argument list is picked.
To call X::X(my_initializer_list<int> lst) you'll have to do X b{my_initializer_list{1,2,3 }};
How the braces {} are interpreted when creating an instance of a class is a language feature. That is, we cannot change it so that the next time they are encountered, our custom my_initializer_list contstructor should be selected. What we can control is what happens once it is selected(assuming that it does gets selected by the language rules).
Can I implement a my_initializer_list in the global scope so that the X b{ 1,2,3 }; calls X::X(my_initializer_list<int> lst)?
So as far as i know, i don't think what you proposed is possible/doable as it requires the {} to be interpreted differently.
Imagine if you provided more than one my_initializer_list say like my_initializer_list1, my_initializer_list2, my_initializer_list3 and suppose your class has a constructor corrresponding to each of them, then when you use {} for creating an instance of the class, how can it be decided which of those constructor to select.

Initialising class members - default values or member-init-list?

I'm a bit confused about when and how to initialize members of a class in a constructor. If I understand things correctly, you can accomplish this by either including default values in the class definition, a constructor member initialiser list, or via assignment in the body of the constructor. I realise that it's best to initialize before entering the body of the constructor, but when should you use a member initialiser list and when should you use default values?
Apologies if I've fundamentally misunderstood something here.
Your are really confusing things a bit. There are at least 4 different concepts
default arguments
initializer list
initialization in constructor body (even constructors can have a non-trivial body)
inline member initialization[1]
you made 3 of.
In the following examples (at ideone.com) I use struct instead of class and inline constructors for brevity/simplicitly.
#include <iostream>
struct A
{
A(int v=1): m(v) {}
int m;
};
struct B
{
B(): m(2) {}
int m;
};
struct C
{
C()
{
m = 3;
}
int m;
};
// since C++11
struct D
{
int m = 11;
};
int main()
{
using namespace std;
#define TRACE(arg) cout << #arg ": " << arg.m << endl;
A a1;
TRACE(a1)
A a2(4);
TRACE(a2)
B b;
TRACE(b)
b.m = 5;
TRACE(b)
C c;
TRACE(c)
c.m = 6;
TRACE(c)
D d;
TRACE(d)
d.m = 0;
TRACE(d)
#undef TRACE
}
The usage of A differs from that of B, C and D.
Only A provides a way to initialize m with a value other than the default. All variants provide direct access to m (mostly not a good choice, see it as a placeholder for a more sophisticated way to modify a member). You have to modify an object if there is no access to the actual initializer value(s). This makes m for constant objects of B, C and D effectively a compile-time constant. Member "initialization" in the constuctor body (C) should be avoided if an initializer list (A or B) can be used (sometimes this is not as trivial as shown here).
Option D should be estimated if you are using C++11 or later.
So A seems the most flexible option. But it's not a good pattern for initializing two or more members: it's too easy to mix up the parameter order.
[1] this option was added after I (thanks to NikosC) realized that this options existed

Unable to use copy initialization (i.e =) to construct class with initializer list

I would have initially expected the following code to compile:
#include <set>
using namespace std;
class A {
public:
set<int> st;
A(set<int> s) : st(s) {}
};
int main() {
A a = {1,2,3}; // Error, couldn't convert from initializer list
A b({1,2,3}); // Ok
}
I can't understand why the first construction fails but the second one succeeds. I tried to replicate the error without the use of initializer list but I couldn't do so.
I am using vc12, but it also fails on the microsoft online visual compiler (with a different error message and code to my compiler).
Should this compile?
If not why not?
EDIT:
The following does compile (EDIT: Only in visual studio and this is a bug.):
class A {
public:
int i;
A(int j) : i(i) {}
};
class B {
public:
A a;
B(A o) : a(o) {}
};
class C {
public:
B b;
C(B u) : b(u) {}
};
int main() {
C c = A(10); // Compiles just fine, but isn't this doing the same set of implicit conversions?
C v(A(10));
}
The two seem the same to me in terms of the number of implicit conversions.
Given this code:
#include <set>
using namespace std;
class A {
public:
set<int> st;
A(set<int> s) : st(s) {}
};
int main() {
A a = {1,2,3}; // Error, couldn't convert from initializer list
A b({1,2,3}); // Ok
}
The declaration of a, using the “=” syntax, is a copy initialization with a brace-enclosed list.
If A were a Plain Old Data type or an “aggregate” then the values in the list would be used to initialize the items of a in order, and the rest of the items, if any, would be zero-initialized.
If A instead had a constructor taking a suitable std::initializer_list argument, then that would have been used.
Those two possibilities are exhaustive. For example, if the constructor with std::set had been considered (it isn't considered), then that would have involved two implicit user-defined conversions to produce the temporary object on the right hand side of =, namely std:initializer_list → std::set, and then std:.set → A. And the rules of C++ limit an implicit conversion sequence to at most one user defined conversion.
And so, since class A is neither POD/aggregate nor a class with std::initializer_list construction, you get an error.
The declaration of b is direct initialization. Here the provided argument is used not to produce a temporary A, but to produce an argument acceptable to some A constructor. One such possibility is found: using the argument to produce a std::set.
When initializing an object with = the compiler implicitly creates a temporary object of your type and then copies it to your variable (although the temporary and copy may, and usually are, elided).
So your a object is theoretically equivalent to:
A a = A(std::set<int>({1, 2, 3}));
I'm not sure exactly why the behaviour differs but I think it's due to the fact that the compiler is allowed to only perform one user defined conversion implicitly. In this case it's probably considered two separate UDCs:
initializer list to std::set
std::set to A
In first case you are trying to construct your object using constructor which takes an std::initializer_list as single parameter (which is not implemented). In your second call a temporary std::set<int> instance is constructed and your A(set<int> s) constructor is called.
In order to fix it implement the missing constructor:
A(std::initializer_list<int> list) : st(list) { }

C++ what is "instantiated from here" error?

I am new to C++ programming, I am wondering what is "instantiated from here" error?
struct Data {
Data(int a, int b) {
x = a;
y = b;
}
int x;
int y;
};
std::map<int, Data> m;
m[1] = Data(1, 2);
I got several error messages
no matching function for call to "Data::Data()"
"instantiated from here" error
Thanks.
There is no default constructor for struct Data. The map::operator[] returns a default constructed instance of its value type, in this case struct Data.
Either supply a default constructor:
Data() : x(0), y(0) {}
or use std::map::insert():
m.insert(std::pair<int, Data>(1, Data(1, 2)));
C++ what is “instantiated from here” error?
That is not an error, but the continuation of the previous error adding extra information. The compiler is adding the error: prefix so that it is easier to read (or parse) what lines belong to the error.
You can read the whole block as a single error:
No matching function call to Data::Data() instantiated from...
You need to provide a constructor which takes no arguments.
Data::Data(){}
It means that the error (the "No matching function call" one) became obvious only when the compiler started resolving (instantiating) the template std::map::map().
When you insert an item into a map using operator[], what happens is that the map inserts a default-constructed object for the specified key to refer to, then the value you're assigning gets copied into that default constructed object.
To create that default-constructed object, a default constructor needs to be available. In your case, sine you've specified a constructor that takes two arguments (and not supplied default values for those arguments), the compiler will not automatically synthesize a default constructor for you.
Under the circumstances, I'd probably modify your constructor to something like this:
Data(int a=0, int b=0) : x(a), y(b) {}
Note that you should generally prefer initializing variables in an initialization list (if possible) over assigning to them in the body of the constructor.
no matching function for call to "Data::Data()"
You need to provide a default constructor. See the answer to this question:
Template Error: no appropriate default constructor available
"instantiated from here" error
You can only have variable declarations in the global area. You need do that assignment from within a function. Correct code:
#include <map>
struct Data {
Data(int a, int b) {
x = a;
y = b;
}
int x;
int y;
Data() {};
};
std::map<int, Data> m;
void main()
{
m[1] = Data(1, 2);
}

gotw 80 syntax - initialisation in parameter list

Gotw 80 includes the following example:
// Example 1
//
#include <string>
using namespace std;
class A
{
public:
A( const string& s ) { /* ... */ }
string f() { return "hello, world"; }
};
class B : public A
{
public:
B() : A( s = f() ) {}
private:
string s;
};
int main()
{
B b;
}
The article goes to discuss why the line s = f() is incorrect - due to object lifetimes and order of construction. The article states that at the time, the error wasn't picked up by the compiler.
However, ignoring the problems of order of intialisation and object lifetime, I don't see how s = f() in the parameter list of the constructor can be legal syntactically - it appears to be trying to initialise a member in the parameter list (or perhaps declaring a default value). Can anyone explain what this syntax is trying to do?
It looks like the intention was to call f() and assign the result to B::s. Afterward, the result of that assignment (which is s) would be used as actual parameter when calling the inherited A constructor.
It's syntactically valid. Replace s in that expression with some non-member variable, and g++ accepts it without issue. You might see similar syntax used more often with ordinary function calls instead of constructor calls.
Syntactically it's legal... when you have a base class with a constructor that takes arguments you can of course pass any expression as parameter:
strut A {
A(int) {}
};
struct B : A {
B() : A( any expression that returns an int ) {}
};
The problem is that when evaluating the expression in the example the object is not even yet a fully constructed A instance, so that code is invalid for two distinct reasons:
Calls a method of A of an non-instance (the constructor didn't start yet): f() call is illegal.
Assigns to a member that hasn't been initialized: s=... is illegal.