Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it best practice to use a initializer_list in assignment

Tags:

c++

c++11

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
like image 916
James Meas Avatar asked Nov 17 '22 09:11

James Meas


1 Answers

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/

like image 110
Persixty Avatar answered Jan 10 '23 17:01

Persixty