direct-initialization vs direct-list-initialization (C++)

Viewed 6755

DIRECT-  VS  COPY-INITIALIZATION
Through this question (Is it direct-initialization or copy-initialization?) I learned the differences between direct-initialization and copy-initialization:

direct-initialization                   copy-initialization
-----------------------                 ---------------------

obj s("value");                         obj s = obj("value");
                                        obj s = "value";

obj s{"value"};                         obj s = {"value"};
                                        obj s = obj{"value"};

I mention it here for the sake of completeness. My actual questions for this page are listed in the next paragraph >>

 
DIRECT-INITIALIZATION  VS  DIRECT-LIST-INITIALIZATION
The answers revealed that within the category of direct-initialization, one can make a difference between direct-initialization and direct-list-initialization.:

obj s("value");   // direct-initialization

obj s{"value"};   // direct-list-initialization

I know that list-initialization doesn't allow narrowing, such that an initialization like int x{3.5}; won't compile. But besides this, I got a couple of questions:

 
(1) Is there any difference in compiler output between
obj s("value"); and obj s{"value"};?
Let's consider a compiler without any optimizations. I would like to know any possible technical difference :-)

 
(2) Perhaps I should ask the exact same question for a multi-variable initialization, like:
obj s("val1", "val2"); and obj s{"val1", "val2"};

 
(3) I have noticed that the list-initialization can sometimes call a different constructor, like in:

vector<int> a{10,20};   //Curly braces -> fills the vector with the arguments
vector<int> b(10,20);   //Parentesis -> uses arguments to parameterize some functionality

How is that possible?  
 

DID WE COVER ALL POSSIBLE INITIALIZATIONS HERE?
From my limited knowledge on C++, I believe that all possible initializations of objects (either native-typed or user-defined-typed objects) have been covered in the examples above. Is that correct? Did I overlook something?  
 


PS: I am learning C++ (I do know C, but not yet C++), so please don't be too hard on me ;-)

2 Answers

 
(1) Is there any difference in compiler output between
obj s("value"); and obj s{"value"};?
Let's consider a compiler without any optimizations. I would like to know any possible technical difference :-)

 
(2) Perhaps I should ask the exact same question for a multi-variable initialization, like:
obj s("val1", "val2"); and obj s{"val1", "val2"};

 
(3) I have noticed that the list-initialization can sometimes call a different constructor, like in:

vector<int> a{10,20};   //Curly braces -> fills the vector with the arguments
vector<int> b(10,20);   //Parentesis -> uses arguments to parameterize some functionality

How is that possible?

  • If there is an initializer-list constructor for the class type obj, it will always be preferred over other other constructors for brace-init-initializers (list initialization), in your case obj s{"value"};;

    What this means is that if you have a constructor that takes std::initializer_list<T> as its first parameter and other parameters are defaulted, then it is preferred. Example

    struct A{
        A(std::initializer_list<std::string>);    //Always be preferred for A a{"value"}
        A(std::string);
    };
    

    std::vector<T> and other STL containers have such initializer-list constructors.

  • Otherwise, Overload resolution kicks in and it falls back to any available constructor as selected by the overload resolution process;

  • Otherwise, if the class has no user defined constructors and it's an aggregate type, it initializes the class members directly.


DID WE COVER ALL POSSIBLE INITIALIZATIONS HERE?
From my limited knowledge on C++, I believe that all possible initializations of objects (either native-typed or user-defined-typed objects) have been covered in the examples above. Is that correct? Did I overlook something?

Nope. you didn't. Excluding Reference initialization, there are five ways objects may be initialized in C++.

  • Direct Initialization
  • List Initialization
  • Copy Initialization
  • Value Initialization
  • Aggregate Initialization (only for aggregate types)

You can find more information here

List-initialization guarantee left-to-right order of arguments evaluation. In this example we will create std::tuple from istream data and then output tuple example can be found here:

#include <iostream>
#include <sstream>
#include <tuple>

template<typename T, typename CharT>
T extract(std::basic_istream<CharT>& is) {
    T val;
    is >> val;
    return val;
}

void print(const std::tuple<int, long, double>& t) {
    using std::cout;
    cout << std::get<0>(t) << " " << std::get<1>(t) << " " << std::get<2>(t) << std::endl;
}

int main()
{
    std::stringstream ss1;
    std::stringstream ss2;
    ss1 << 1 << " " << 2 << " " << 3;
    ss2 << 1 << " " << 2 << " " << 3;
    auto compilerOrder    = std::tuple<int, long, double>( extract<int>(ss1), extract<long>(ss1), extract<double>(ss1) );
    auto leftToRightOrder = std::tuple<int, long, double>{ extract<int>(ss2), extract<long>(ss2), extract<double>(ss2) };
    print(compilerOrder);
    print(leftToRightOrder);
}

Output:

3 2 1
1 2 3

As you can see, the difference will be seen then we use multiple times same stream-like resource inside function brackets.

Also my question about that

Related