Consider the following demonstrative program.
#include <iostream>
struct A
{
int x;
explicit A( int x = 0 ) : x( x ) {}
};
int main()
{
A a1 { 10 };
std::cout << "a1.x = " << a1.x << '\n';
// A a2 = { 10 };
}
In this declaration
A a1 { 10 };
there is used the direct initialization.
And in the commented declaration
// A a2 = { 10 };
that can be also rewritten like
// A a2 = 10;
there is used the copy-initialization. But the constructor declared with the specifier explicit
. So the compiler will issue an error. That is it is unable to convert the integer object 10
to the type of A
implicitly.
You could write instead
A a2 = A{ 10 };
That is calling the constructor explicitly.
Of course for fundamental types there is no difference because neither constructor is applied except that narrowing conversion is not allowed when the braced initialization is used as for example
int x { 1.0 };
There is a big difference when the type specifier is the placeholder auto
.
For example
auto x = 10;
x
has the type int
.
auto x { 10 };
x
again has the type int
.
auto x = { 10 };
Now x
has the type std::initializer_list<int>
.
For example you could rewrite your loop
for (unsigned int counter{ 1 }; counter <= 20; ++counter) {
the following way
for ( auto counter{ 1u }; counter <= 20; ++counter) {
but you may not write
for ( auto counter = { 1u }; counter <= 20; ++counter) {
because in this case the type of the variable counter
is std::initializer_list<unsigned int>
.
So in general you have the following forms of initializations
T x = value;
T x = { value };
T x( value );
T x { value };
For example
#include <iostream>
int main()
{
int x1 = 1;
std::cout << "x1 = " << x1 << '\n';
int x2 = ( 2 );
std::cout << "x2 = " << x2 << '\n';
int x3( 3 );
std::cout << "x3 = " << x3 << '\n';
int x4{ 4 };
std::cout << "x4 = " << x4 << '\n';
}
The program output is
x1 = 1
x2 = 2
x3 = 3
x4 = 4
But there is one more situation when instead of T()
you should use T{}
as an initializer. It is when template functions are used.
Consider the following demonstrative program
#include <iostream>
template <class>
void f()
{
std::cout << "f<T>() is called\n";
}
template <int>
void f()
{
std::cout << "f<int>() is called\n";
}
int main()
{
f<int()>();
f<int{}>();
}
Its output is
f<T>() is called
f<int>() is called
The construction int()
used as a template argument specifiers the type template argument int
while the construction int{}
used as a template argument specifiers a non-type template argument of the type int
equal to 0
.