I have read (Bjarne Stroustrup, The C++ Programming Language, 6.3.5) about using initializer_list when initializing a variable, so that you don't have a narrowing conversion. Bjarne recommends only using direct-list-initialization :
X a1 {v};
X a2 = {v};
X a3 = v;
X a4(v);
Of these, only the first can be used in every context, and I strongly recommend its use. It is clearer and less error-prone than the alternatives.
Why does Bjarne only recommend the first one?
Why isn't it recommended to do initializer_list in assignment (rather than initialization)? Or is it just implied that you should do that?
a1 = {v};
Here is an example of what I am asking about? Why is initializer_list not recommended for assignment (from what I can tell) but it is recommended for initialization? It seems like it is beneficial by reducing potential narrowing conversions on accident.
char c;
int toobig = 256;
c = 256; //no error, narrowing occurs
c = { toobig }; //narrowing conversion, error
Initializer lists are usually recommended and overcome a syntactic trap called 'The Most Vexxing Parse'.
std::vector<int> v();
Inside a function this looks like variable declaration but it's a declaration of a function v
taking no arguments returning a std::vector<int>
.
OK so std::vector<int> v;
fixes that one but in a template a parameter may be a built in type where int x;
leaves x
uninitialized but int x{};
(value) initializes it to zero.
In modern C++ with copy elision and a couple of syntatic rules, there aren't that many ways to accidentally create temporary copies in a variable declaration.
But initializer-lists do clear up a couple of anomalies and are to be recommended.
Overloading in C++ is quite keen and there remain reasons to sometimes use ()
to call the appropriate constructor.
For example std::vector<T>
can take a initializer-list of values and create an array containing that list. Great.
It can also take count
and value
arguments and create an array of count
copies of `value'. Also great.
But if the size type is compatible with the value-type (T
) you can still get a surprise!
#include <iostream>
#include <vector>
template<typename T>
void dump_vector(const std::string& tag,const std::vector<T>& vec);
int main() {
std::vector<int> v1(5,20);
std::vector<int> v2{5,20};
std::vector<std::string> v3(5,"Hi!");
std::vector<std::string> v4{5,"Hi!"};
dump_vector("v1",v1);
dump_vector("v2",v2);
dump_vector("v3",v3);
dump_vector("v4",v4);
return 0;
}
template<typename T>
void dump_vector(const std::string& tag,const std::vector<T>& vec){
std::cout<< tag << "={ ";
auto begin=vec.begin();
auto end=vec.end();
for(auto it{begin};it!=end;++it){
if(it!=begin){
std::cout<<", ";
}
std::cout<<*it;
}
std::cout << " }\n";
}
Expected output:
v1={ 20, 20, 20, 20, 20 }
v2={ 5, 20 }
v3={ Hi!, Hi!, Hi!, Hi!, Hi! }
v4={ Hi!, Hi!, Hi!, Hi!, Hi! }
The versions with ()
and {}
did different things for std::vector<int>
but landed on the same constructor for std::vector<std::string>
.
This isn't a new or unique problem. C++ uses types heavily in selecting an overload and when there's a bunch of candidates different template instantiations might make an unexpected choice!
I'd say initializer-list is now preferred. But when a constructor that itself takes in initializer list exists, you may need to be explict if you don't want to hit it.
Also worth a read http://read:%20https://herbsutter.com/2013/05/09/gotw-1-solution/
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With