Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Empty braces magic in initializer lists

Consider the following minimal example:

#include <iostream>

struct X {
  X() { std::cout << "Default-ctor" << std::endl; }
  X(std::initializer_list<int> l) { 
      std::cout << "Ilist-ctor: " << l.size() << std::endl; 
  }
};

int main() {
    X a{};
    X b({}); // reads as construct from {}
    X c{{}}; // reads as construct from {0}
    X d{{{}}}; // reads as construct from what?
    // X e{{{{}}}}; // fails as expected
}

Godbolt example

I have no questions about a, b and c, everything is rather clear

But I can not understand why d works

What this additional pair of braces in d stands for? I looked up C++20 standard, but I can not find answer easily. Both clang and gcc agree on this code, so it is me who misses something

like image 484
Konstantin Vladimirov Avatar asked Dec 15 '20 01:12

Konstantin Vladimirov


People also ask

What do empty brackets mean in C++?

What do empty brackets mean in struct declaration? T object {}; is syntax for value initialization (4). Without the curly brackets, the object would be default initialized. To be pedantic, this is not a "struct declaration". It is declaration of a variable.

Is initializer list faster?

Conclusion: All other things being equal, your code will run faster if you use initialization lists rather than assignment.

What are the advantages of initializer list?

The most common benefit of doing this is improved performance. If the expression whatever is the same type as member variable x_, the result of the whatever expression is constructed directly inside x_ — the compiler does not make a separate copy of the object.

How do initializer lists work?

An initializer list starts after the constructor name and its parameters. The list begins with a colon ( : ) and is followed by the list of variables that are to be initialized – all of​ the variables are separated by a comma with their values in curly brackets.


Video Answer


1 Answers

A nice trick to do to get information about what the compiler does, is to compile using all errors: -Weverything. Let's see the output here (for d only):

9.cpp:16:6: warning: constructor call from initializer list is incompatible with C++98                                                                                            
      [-Wc++98-compat]                                                                                                                                                            
  X d{{{}}}; // reads as construct from what?                                                                                                                                     
     ^~~~~~                           

X::X(std::initializer_list) is called.

9.cpp:16:8: warning: scalar initialized from empty initializer list is incompatible with                                                                                          
      C++98 [-Wc++98-compat]                                                                                                                                                      
  X d{{{}}}; // reads as construct from what?                                                                                                                                     
       ^~                               

Scalar (int) initialized in inner {}. So we have X d{{0}}.

9.cpp:16:7: warning: initialization of initializer_list object is incompatible with                                                                                               
      C++98 [-Wc++98-compat]                                                                                                                                                      
  X d{{{}}}; // reads as construct from what?                                                                                                                                     
      ^~~~                                                                                                                                                                        
5 warnings generated.                                                                                                                                                             

std::initializer_list is initialized from {0}. So we have X d{std::initializer_list<int>{0}};!

This shows us everything we need. The extra bracket is for constructing the initializer list.

Note: If you want to add extra brackets you can by invoking the copy/move constructor (or eliding it), but C++ compilers won't do it implicitly for you to prevent errors:

X d{X{{{}}}}; // OK
X e{{{{}}}}; // ERROR
like image 126
Kostas Avatar answered Sep 23 '22 07:09

Kostas