5

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
Community
  • 1
  • 1
James Meas
  • 327
  • 4
  • 16
  • 1
    Feel the difference between assignment and initialization=) `X a1{v}` is the perfect RAII – Smit Ycyken Jun 15 '18 at 07:55
  • 7
    Assignment `a1 = {v};` would involve construction of a temporary object to be assigned. And may potentially be ill-formed if corresponding `X` constructor is declared as `explicit` (which is a good practice). – user7860670 Jun 15 '18 at 07:59
  • Possible duplicate of [Is there a difference between copy initialization and direct initialization?](https://stackoverflow.com/questions/1051379/is-there-a-difference-between-copy-initialization-and-direct-initialization) – Joseph D. Jun 15 '18 at 08:14
  • I'm wondering about assignment using initializer_list rather than initialization using initializer_list. Is that the same thing? I thought there was a difference. – James Meas Jun 15 '18 at 08:17
  • 2
    Take everything you hear about C++ with a pinch of salt. – Ron Jun 15 '18 at 08:23
  • I put a little edit about what I'm trying to ask. – James Meas Jun 15 '18 at 08:24
  • Just use explicit constructors: `c = char{256};` Also note that `operator =` can be overloaded (for classes) so specifying type explicitly will help to resolve ambiguity. – user7860670 Jun 15 '18 at 08:30
  • it's copy ctor not assginment he wants to get rid of as in OP's examples. – Hello Everyone Jun 15 '18 at 08:34
  • I personally think that while the initialiser list has it's uses, when your constructor want's to do something with the arguments rather than just store them, I think the constructor without the initialisation list makes the intent much clearer. – UKMonkey Jun 15 '18 at 08:40
  • You might instead want to check the warning level set for your compiler. I get a warning that [256 will not fit in a `char`](https://learn.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-2-c4309). – Bo Persson Jun 15 '18 at 09:38
  • The example you give, `a1 = {v};` is not an initialization of `a1`, though it involves initializing a temporary. Why would Stroustrup discuss it when talking about initialization of named variables? – Arne Vogel Jun 15 '18 at 17:43
  • I know, that's why I asked why he doesn't mention assignment with initializer_list anywhere in the book. – James Meas Jun 15 '18 at 18:17
  • I'm not sure that you are asking for, or really want, an `initializer_list`. The precise spelling and method of spacing is very important here, given how many closely named but very different concepts C++ has. If you say "initializer_list", people will think you mean `std::initializer_list`, and that is not what you're really asking about, only one thing that can potentially win overload resolution when using list-initialisation or -assignment, which is the real thing here. – underscore_d Jun 15 '18 at 19:47
  • Ok I might be mistaken, I thought that was the same. I'm talking about the { }, does that have a name? Why is it only recommended to use { } in initalization but not in assignment? – James Meas Jun 15 '18 at 21:30

2 Answers2

1

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/

Persixty
  • 8,165
  • 2
  • 13
  • 35
-1

Take this as example

#include <iostream>

struct foo
{
    explicit foo(int)
    {
        std::cout << "[+] c'tor called\n";
    }

    foo(const foo&)
    {
        std::cout << "[+] copy c'tor called\n";
    }

};

int main()
{

    std::cout << "\ncreating object a\n";
    foo a = foo{1};

    std::cout << "\n\ncreating object b\n";
    foo b{1};

}

Compiling with g++ main.cpp --std=c++11 -fno-elide-constructors

Output:

creating object a
[+] c'tor called
[+] copy c'tor called

creating object b
[+] c'tor called

In the first a temporary is created and then a is created by calling the copy constructor.

Whereas in second example the object is directly created.

There are few cases where you have to use () syntax instead of {}.

Refer to Scott Meyers-Effective Modern C++ (item 7)`

Atul
  • 546
  • 4
  • 16