Wednesday, September 27, 2023

initializer_list and list - initialization

Overview
list initialization enables uniform way of initializing scalar and complex objects such as intsdoubles, structs, classes using a pair of braces {}.  A new data type called initializer_list is used for such initialization.

Details
initializer_list
The C++11 standard library introduced a new template based type initializer_list <T> to handle variable-length {}-lists. initializer_list is used to aid uniform initialization. It's automatically created when braces {} are used to initialize.  

The initializer_list  can be also instantiated to define homogeneous lists of varying lengths and passed as arguments. Note that these are not mutable. They can be iterated using iterators. Indexing is not possible.
auto x0 = {}; // error (no element type)
auto x1 = {1}; // initializer_list<int>
auto x2 = {1.,2.}; // initializer_list<double>
auto x3 = {"1","2","3"}; // initializer_list<char*>
auto x4 = {1,2.0}; // error : nonhomogeneous list
*x2.begin()=10; //error 

int high_value(initializer_list<int> val)
{
    int high = numeric_traits<int>lowest();
    if (val.size()==0) 
        return high;

    for (auto x : val)
        if (x>high) 
            high = x;

    return high;
}
int v1 = high_value({1,2,3,4,5,6,7});
int v2 = high_value({-1,2,v1,4,-9,20,v1});

Classes can also provide a constructor accepting initializer_list.  In this case, the compiler will prefers this type of constructor for qualified list construction.
struct  P
{
    int i;
    int j;
    int k;

    P(int, int){}
P(initializer_list<int> lst) { i=j=k=0; auto itr = lst.begin(); if (itr != lst.end()) { i=*itr++; } if (itr != lst.end()) { j=*itr++; } if (itr != lst.end()) { k=*itr++; } } }; P p{1,2,3}; //i=1 j=2 k=3 P p2={4,5}; //i=1 j=2. will not call P(int, int)

list - initialization
eliminates most vexed parsing
mvp happens during  compilation of code such as below. 
The compiler throws an error as it cannot resolve the line highlighted below is a variable declaration or a function declaration. 
TimeKeeper time_keeper(Timer ()); //error
This is depicted in the example 11.

After uniform initialization as shown below, this code compiles without errors.
TimeKeeper time_keeper(Timer {});
This is depicted in the example 12.

eliminates narrowing errors
Enables overflow checks. During compilation, the compiler reports errors for c, i and s for overflow.
char  {0xf0};    //narrowing error
short {0xffff};  //narrowing error
int   {2.0};     //narrowing error

unqualified lists
A unqualified list is used where an expected type is unambiguously known. It can be used as an
expression only as:
  • A function argument
  • A return value
  • The right-hand operand of an assignment operator (=, +=, ∗=, etc.)
  • A subscript
int v {7}; // initializer (direct initialization)
int v2 = {7}; // initializer (copy initialization)
int v3 = m[{2,3}]; // assume m takes value pairs as subscripts
v ={8}; //right-hand operand of assignment
v += {88}; //right-hand operand of assignment
{v} = 9; // error : not left-hand operand of assignment
v = 7+{10}; // error : not an operand of a non-assignment operator
f({10.0}); // function argument
return {11}; // return value

qualified lists
{}-list can be  used as constructor arguments, as if used in a ()-list. List elements are not copied except as by-value constructor arguments.
struct S { int a; double  b; };
S v {7,8.5}; //direct initialization of a variable
v = S{7,8.5}; // assign using qualified list
S* p = new S{7,8.5}; // construct on free store using qualified list

uniform container initialization 
Containers such as vectorlist and  map can be initialized with a list of the objects they are intended to contain.
std::vector<int>          {1,2,3,4,5};
std::vector<int>          {1,2.0,3,4,5}; //error 2.0 is not int
std::list<std::string>    {"mon","tue","wed","thu","fri","sat","sun"};
std::map<std::string,int> {{"a",97},{"b",98},{"c",99}};

//internally compilers generates code for the assignment
//for example vector<double> v = {1, 2, 3.14};
//becomes similar to
//const double temp[] = {double{1}, double{2}, 3.14 } ;
//const initializer_list<double> tmp(temp,sizeof(temp)/sizeof(double));
//vector<double> v(tmp);

usages examples
In the example 13 below and as seen in its console output. the compiler initializes the data members of the  POD object based on their position in the class.

In the example 14 below and seen in its console output. the compiler calls different constructors of POD based on the data types.

In the example 15 below and its console output. the POD is constructed using  an initializer_list constructor.

Side effects 
The STL container classes such as vector provide list initialization constructors. However it can introduce unwanted side effects as shown in the example 16 and its console output below. Therefore parenthesis () based constructors should be used where needed. 



No comments:

Post a Comment