initializer lists

S

Stefan Ram

The following code initializes the ::std::string "AA"s.

::std::string const str{ 65, 'A' };

. In ::std::basic_string, there is the constructor

basic_string( initializer_list< charT >, const Allocator& = Allocator() );

, but { 65, 'A' } seems to have the type ::std::initializer_list< int >.

Is it converted into initializer_list< char > somehow?
 
V

Victor Bazarov

The following code initializes the ::std::string "AA"s.

::std::string const str{ 65, 'A' };

. In ::std::basic_string, there is the constructor

basic_string( initializer_list< charT >, const Allocator& = Allocator() );

, but { 65, 'A' } seems to have the type ::std::initializer_list< int >.

Is it converted into initializer_list< char > somehow?

Every 'int' in that list is converted to 'char', most likely. It's
quite possible that 'std::initializer_list<T>' is convertible to
'std::initializer_list<U>' if 'T' is convertible to 'U'. Too lazy to
dig in the Standard for a confirmation.

V
 
L

Luca Risolia

Victor said:
It's
quite possible that 'std::initializer_list<T>' is convertible to
'std::initializer_list<U>' if 'T' is convertible to 'U'. Too lazy to
dig in the Standard for a confirmation.

std::cout << std::is_convertible<std::initializer_list<int>,
std::initializer_list<char>>::value; // 0
 
V

Victor Bazarov

std::cout << std::is_convertible<std::initializer_list<int>,
std::initializer_list<char>>::value; // 0
Ok, thanks for that.

I looked in the Standard, and it defines a constructor for
'basic_string' class template from 'std::initializer_list<charT>', not
from 'std::initalizer_list<int>'. So, to me it's obvious that the
compiler should try to interpret the {65, 'A'} notation as the
initializer list of char, not of int, and from that it will initialize
the string object (as if from a pair of iterators).

Now, again due to laziness I haven't found a confirmation for that in
the Standard. What allows the compiler to interpret that initializer
list as initializer_list<char> instead of <int>, I am not sure, but
apparently something does :)

V
 
M

Martin Shobe

Ok, thanks for that.

I looked in the Standard, and it defines a constructor for
'basic_string' class template from 'std::initializer_list<charT>', not
from 'std::initalizer_list<int>'. So, to me it's obvious that the
compiler should try to interpret the {65, 'A'} notation as the
initializer list of char, not of int, and from that it will initialize
the string object (as if from a pair of iterators).

Now, again due to laziness I haven't found a confirmation for that in
the Standard. What allows the compiler to interpret that initializer
list as initializer_list<char> instead of <int>, I am not sure, but
apparently something does :)

If I'm reading it correctly, it's in 8.5.4.3 and 8.5.4.2 (n3242).

8.5.4.2 states, "A constructor is an initializer-list constructor if its
first parameter is of type std::initializer_list<E> or reference to
possibly cv-qualified std::initializer_list<E> for some type E, and
either there are no other parameters or else all other parameters have
default arguments (8.3.6)."

8.5.4.3 states, "if T is a class type, constructors are considered. If T
has an initializer-list constructor, the argument list consists of the
initializer list as a single argument"

std::string has an initializer-list constructor and the type of the
element of that constructor's argument is char, so it uses
initializer_list<char>.

Martin Shobe
 
S

Stefan Ram

Martin Shobe said:
8.5.4.3 states, "if T is a class type, constructors are considered. If T
has an initializer-list constructor, the argument list consists of the
initializer list as a single argument"
std::string has an initializer-list constructor and the type of the
element of that constructor's argument is char, so it uses
initializer_list<char>.

I begin to see a pattern that distinguishes a
braced-init-list from an instance of ::std::initializer_list!

A braced-init-list is still open for interpretation as an
instance of ::std::initializer_list<T> with several
possibilities for the type T.

Once it has become an instance of ::std::initializer_list,
T is fixed.

For example,

of these two lines, each line is allowed:

::std::initializer_list< char >( { 65 } );

::std::initializer_list< char >il{ 65 }; ::std::string s = il;

; of these two lines, each line is /not/ allowed:

::std::initializer_list< char >( ::std::initializer_list< int >( { 65 } ));

::std::initializer_list< int >il{ 65 }; ::std::string s = il;

. There must be a paragraph in the standard that says
something like:

When a braced-init-list is used as the initializer for
an instance of type ::std::initializer_list<T>, where T
is a fixed type (not a template parameter), then each
element of the ::std::initializer_list<T> (has type T
and )is initialized (as if )by
direct-list-initialization from the corresponding
element of the braced-init-list.

One hint might be n3797,8.5.4:

»List-initialization is initialization of an object or
reference from a braced-init-list.«

It says »from a braced-init-list«, not »from an instance of
»::std::initializer_list«!

Uh, now I see this:

»A constructor is an initializer-list constructor if its
first parameter is of type std::initializer_list<E> or
reference to possibly cv-qualified
std::initializer_list<E> for some type E, and either
there are no other parameters or else all other
parameters have default arguments (8.3.6).« n3797,8.5.4p2

and then

»List-initialization of an object or reference of type T
is defined as follows:« n3797,8.5.4p3

»An object of type std::initializer_list<E> is
constructed from an initializer list as if the
implementation allocated a temporary array of N elements
of type const E, where N is the number of elements in
the initializer list. Each element of that array is
copy-initialized with the corresponding element of the
initializer list, and the std::initializer_list<E>
object is constructed to refer to that array.« 3797,8.5.4p5

I think that this 8.5.4p5 (that I just quoted) is the key,
it also has an example: »

struct X {
X(std::initializer_list<double> v);
};

X x{ 1,2,3 };

The initialization will be implemented in a way roughly
equivalent to this:

const double __a[3] = {double{1}, double{2}, double{3}};
X x(std::initializer_list<double>(__a, __a+3));«.

Note that 8.5.4p5 says:

»An object of type std::initializer_list<E> is
constructed from an initializer list«

, thus, an »object of type std::initializer_list<E>« is
/not/ the same as »an initializer list«! It is /constructed
from/ an an »initializer list«! »initializer list« here
seems to refer to what is syntactically a »braced-init-list«.
 
L

Luca Risolia

Victor said:
I looked in the Standard, and it defines a constructor for
'basic_string' class template from 'std::initializer_list<charT>', not
from 'std::initalizer_list<int>'. So, to me it's obvious that the
compiler should try to interpret the {65, 'A'} notation as the
initializer list of char, not of int, and from that it will initialize
the string object (as if from a pair of iterators).

Now, again due to laziness I haven't found a confirmation for that in
the Standard. What allows the compiler to interpret that initializer
list as initializer_list<char> instead of <int>, I am not sure, but
apparently something does :)

Using std::initializer_list<int> is okay as long as there is no narrowing
conversion from int to charT, otherwise the code is ill-formed and requires a
diagnostic from the compiler (warning or error):

struct String {
String(std::initializer_list<char>) {}
};

int main() {
int a = 1;
String s{a,'2'}; // GCC warning: narrowing conversion from int to char
}
 
V

Victor Bazarov

Isn't std::string str{ 65, 'A' }; simply the exact same thing as
std::string str(65, 'A')?

No. 8.5/1 (grammar for "braced-init-list") and 8.5.4 List Initialization.

V
 
L

Luca Risolia

Paavo said:
No. One contains 2 characters and the other 65.

That's evident only if you know that the constructor accepting a
std::initializer_list<> overshadows other constructor overloads. If there were
no such constructor, str{65, 'A'} would be equal to str(65, 'A'):

struct String : public std::string {
String(size_type count, char ch) : std::string(count, ch) {}
};

int main() {
String str1{65, 'A'};
String str2(65, 'A');
assert(str1 == str2);
}

There are few exceptions to the above rule: the most important one is empty
braces "{}" which always implies a call to the default constructor.
 
L

Luca Risolia

Chris said:
As does not providing any braces or parenthesis at all (which is what
all experienced C++98 programmers do once they have been bitten by "the
most vexing parse" for the first time):

String str;

will do just fine.

I do not think that being able to pass empty braces is of any practical
value: are there any real-life use cases for it?

If I remember well, "{}" requires a value-initialization of the object being
constructed, while not providing any braces at all does not.

As an example, that implies it can be used as a way to zero-initialize all
scalar data members of a POD. It's subjective if this is practical:

struct S {
int scalar1 /*, ...*/ ;
};

S v[100]{};

for (const auto& e : v)
assert(e.scalar1 == 0);
 
Ö

Öö Tiib

I do not think that being able to pass empty braces is of any practical
value: are there any real-life use cases for it?

Consider case of aggregates:

std::array<int, 3> a1{{}}; // default-initialized
std::array<int, 3> a2 = {}; // default-initialized
std::array<int, 3> a3; // uninitialized
 
L

Luca Risolia

Chris said:
This perhaps illustrates what an unholy mess C++ object initialization
is, and why it is a disgrace to the language. Even brace
initialization is not really uniform initialization, because even in
the case of non-aggregates its behaviour depends on whether the type
under construction has an initializer list constructor or not. It is
this kind of thing that makes C++ very easy to make fun of.

Initialization with braces makes the initialization "uniform" because the use
of braces has the same meaning in any context: from initializing dynamically
allocated arrays to initializing data members, temporaries, etc...while
initialization in C++98/03 is not uniform because parentheses have different
meanings depending on the context. For example:

std::string q(char(), char(c));

Can you immediately see what the type of q is? It would certainly be easier to
guess that q is NOT a variable if you had the good practice to use braces
whenever you intend to initialize a variable.
 
S

Stefan Ram

Luca Risolia said:
Can you immediately see what the type of q is? It would certainly be easier to
guess that q is NOT a variable if you had the good practice to use braces
whenever you intend to initialize a variable.

Except when one wants to initialize

::std::string variable( 10, ' ' );

or

auto variable = 10;
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,582
Members
45,057
Latest member
KetoBeezACVGummies

Latest Threads

Top