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

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 is buggy in this regard 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".


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

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!:)