What is the correct way to initialize a variable in C++
Asked Answered
S

3

8

I have the following code :

bool c (a == b);

and

bool c {a == b};

where a and b are some variables of same type.

I want to know that, what is the difference in above two initializations and which one should be preferred in what conditions ? Any kind of help will be appreciated.

Stauffer answered 20/8, 2015 at 9:57 Comment(0)
W
12

Both forms are direct initialization.

Using curly braces {} for initialization checks for narrowing conversions and generates an error if such a conversion happens. Unlike (). (gcc issues a warning by default and needs -Werror=narrowing compiler option to generate an error when narrowing occurs.)

Another use of curly braces {} is for uniform initialization: initialize both types with and without constructors using the same syntax, e.g.:

template<class T, class... Args>
T create(Args&&... args) {
    T value{std::forward<Args>(args)...}; // <--- uniform initialization + perfect forwarding
    return value;
}

struct X { int a, b; };
struct Y { Y(int, int, int); };

int main() {
    auto x = create<X>(1, 2);    // POD
    auto y = create<Y>(1, 2, 3); // A class with a constructor.
    auto z = create<int>(1);     // built-in type
}

The only drawback of using curly braces {} for initialization is its interaction with auto keyword. auto deduces {} as std::initializer_list, which is a known issue, see "Auto and braced-init-lists".

Waggle answered 20/8, 2015 at 10:0 Comment(6)
So, in this case, it shouldn't matter what we use ? While in cases where we do long to int or whenever there are chances of data loss, it should throw an exception / error ?Whitehead
@cruskal if == is overloaded to return something other than bool, {} will generate an error.Waggle
Also, beware of braced initializers being matched to std::initializer_list in constructors, e.g std::vector<int> c{a,b} vs std::vector<int> c(a,b)Eucharist
@juanchopanza It is a known gcc bug: gcc.gnu.org/bugzilla/show_bug.cgi?id=55783Waggle
@MaximEgorushkin Yes, i did not think about that, and what should be preferred way while writing a program ?Whitehead
@cruskal Prefer using stricter options if possible.Waggle
K
3

First one is the C++03 style direct initialization. The second is C++11 style direct initialization, it additionally checks for narrowing conversions. Herb Sutter recommends the following in new code:

auto c = <expression>;

or when you want to commit to specific type T:

auto c = T{<expression>};

One known drawback with curly braces when T is some class with overloaded constructor, where one constructor gets std::initializer_list as parameter, std::vector for example:

auto v = std::vector<int>{10}; // create vector<int> with one element = 10
auto v = std::vector<int>(10); // create vector<int> with 10 integer elements
Katykatya answered 20/8, 2015 at 10:8 Comment(8)
The second one is such a silly recommendation. What does it gain you? The only thing it does is impose more constraints on T.Dannydannye
@Dannydannye The reasoning from Sutter's original article is: Consider declaring local variables auto x = type{ expr }; when you do want to explicitly commit to a type. It is self-documenting to show that the code is explicitly requesting a conversion, it guarantees the variable will be initialized, and it won’t allow an accidental implicit narrowing conversion. Only when you do want explicit narrowing, use ( ) instead of { }.Ingeminate
Although even he goes on to say: The jury is still out on whether to recommend this one wholesale, as we’re still trying it out, but it does offer some advantages and I suggest you try it out for a while and see if it works well for you.Ingeminate
T x{expr} is still shorter to type and read than auto x = T{expr}.Waggle
@Ingeminate That (the first quote) sounds like nonsense. T x{expr}; commits you to a type and is as self-documenting as the auto version. And it does not require an available copy constructor.Dannydannye
Also, that is Herb Sutter, not Scott Meyers.Dannydannye
@Dannydannye The self-documentation comment is referring to the difference between the auto x = T{expr} and auto x = expr constructs. But I agree with you, I'm just playing devils advocate; it seems like going overboard on auto usage for the sake of it.Ingeminate
@Dannydannye There is also a consistency argument. If you have a bunch of initializations, some where you want to track type and some where you want to commit to a specific type there is an argument that it is easier to read if they consistently use auto.Eucharist
U
2

Now we have five forms of initializations. They are

T x = expression;
T x = ( expression );
T x (  expression );
T x = { expression };
T x { expression };

Each of the forms has its own peculirities. :)

For example let's assume that you have the following declarations in the global namespace

int x;

void f( int x ) { ::x = x; }
int g() { return x ; }
long h() { return x; } 

then in main you can write

int main()
{
    int x ( g() );
}

This code will compile successfully.

However a programmer by mistake made a typo

int main()
{
    int x; ( g() );
         ^^
}

Oops! This code also compiles successfully.:)

But if the programmer would write

int main()
{
    int x = ( g() );
}

and then make a typo

int main()
{
    int x; = ( g() );
         ^^
}

then in this case the code will not compile.

Well let's assume that the programmer decided at first to set a new value for the global variable x before initializing the local variable.

So he wrote

int main()
{
    int x ( f( 10 ), g() );
}

But this code does not compile!

Let's insert equality sign

int main()
{
    int x = ( f( 10 ), g() );
}

Now the code compiles successfully!

And what about braces?

Neither this code

int main()
{
    int x { f( 10 ), g() };
}

nor this code

int main()
{
    int x = { f( 10 ), g() };
}

compiles!:)

Now the programmer decided to use function h(), He wrote

int main()
{
    int x ( h() );
}

and his code compiles successfully. But after a time he decided to use braces

int main()
{
    int x { h() };
}

Oops! His compiler issues an error

error: non-constant-expression cannot be narrowed from type 'long' to 'int' in initializer list

The program decided to use type specifier auto. He tried two approaches

int main()
{
    auto x { 10 };
    x = 20;
}    

and

int main()    
{
    auto x = { 10 };
    x = 20;
}    

and ...some compilers compiled the first program but did not compile the second program and some compilers did not compile the both programs.:)

And what about using decltype?

For example the programmer wrote

int main()
{
    int a[] = { 1, 2 };
    decltype( auto ) b = a;
}    

And his compiler issued an error!

But when the programmer enclosed a in parentheses like this

int main()
{
    int a[] = { 1, 2 };
    decltype( auto ) b = ( a );
}    

the code compiled successfully!:)

Now the programmer decided to learn OOP. He wrote a simple class

struct Int
{
    Int( int x = 0 ) : x( x ) {}
    int x;
};
    
int main()
{
    Int x = { 10 };    
}    

and his code compiles successfully. But the programmer has known that there is function specifier explicit and he has decided to use it

struct Int
{
    explicit Int( int x = 0 ) : x( x ) {}
    int x;
};
    
int main()
{
    Int x = { 10 };    
}    

Oops! His compiler issued an error

error: chosen constructor is explicit in copy-initialization

The programmer decided to remove the assignment sign

struct Int
{
    explicit Int( int x = 0 ) : x( x ) {}
    int x;
};
    
int main()
{
    Int x { 10 };    
}    

and his code compiled successfully!:)

Unintelligible answered 20/8, 2015 at 10:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.