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

2

28

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 ;-)

Consignor answered 18/11, 2017 at 13:8 Comment(2)
"I know that list-initialization doesn't allow narrowing, such that an initialization like int x{3.5}; won't compile." This isn't really true. The Standard only mandates a diagnostic, but compilers are totally allowed to carry on and compile the code. For example, g++ will warn but continue. Anyway, IMO this question is too broad, and its many individual elements are already answered elsewhere. And if you want to know whether the compiler will produce different code... just try it?Everybody
Hi @underscore_d, I agree with you that this question is quite broad. But I think C++ is to blame for that. The language provides soooo many ways to initialize stuff - sometimes the differences are subtle but have some impact, sometimes the differences are only syntactical. All of this is quite overwhelming to someone like me (a C++ beginner). The many individual elements are indeed answered elsewhere, and that is precisely the problem. The info is scattered around. I would like to have it all in one place... :-)Consignor
B
16

 
(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

Bifoliate answered 18/11, 2017 at 13:17 Comment(5)
I would like to add, that list-initialization guarantee left-to-right order of arguments evaluation, so if you need to extract arguments from stream-like resource, you should do it outside constructor brackets or use list-initialization.Anatto
Hi @SemyonBurov, very interesting point you make there. I didn't know that. Could you please explain more, perhaps give some example? It would be great to post it in an answer and gain some well-deserved points ;-)Consignor
Hi @WhiZTiM, thank you very much for your interesting answer. So am I right to say that list-initialization is a mechanism that allows constructor overloading? A mechanism to differentiate vector<int> a{10,20}; from vector<int> b(10,20);. Or is this way of looking at list-initialization too simplistic?Consignor
@Consignor I will add it as an answer thenAnatto
@K.Mulier, the concept of list-initialization is to enable populating containers or objects with elements during initialization; If you check out the rules governing its behavior, you'll find out that it could fall back (though not technically admissible) to other initialization methods. So if there was no initializer-list constructor in the Standard Library's std::vector, then vector<int> a{10,20}; and vector<int> b(10,20); would effectively be the same. So, yes, providing such initializer-list constructors makes a the difference.Bifoliate
A
10

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

Anatto answered 18/11, 2017 at 15:8 Comment(1)
Very interesting!Consignor

© 2022 - 2024 — McMap. All rights reserved.