Question:
Can you please tell me if there is a difference between using round and curly braces when initializing a constructor inside a class. Both options work correctly.
class A
{
public:
char c;
int d;
A(char ch)
:c(ch) {}
A(char ch, int i)
:c{ch}, d{i} {}
};
int main()
{
A first = A('a');
A second = A('b', 1);
cout << "First: " << first.c << endl;
cout << "Second: " << second.c << ' ' << second.d << endl;
return 0;
}
Answer:
Let's start with the fact that there are cases when you can not apply the initialization of one of the types to the data members of the class.
For example, if you have a class member that is an aggregate (structure or array), then the possible types of initialization are limited.
Let's look at a few examples.
In this example, inside the structure A
, an aggregate data member B
is declared, which has the type of the structure. Then this constructor declaration will be incorrect.
struct A
{
A(int x) : b( x ) {}
struct B
{
int x;
} b;
};
int main()
{
A a( 10 );
}
The compiler will issue an error message saying that it cannot convert an object of type int
to an object of type struct B
. However, if you replace the parentheses with curly ones,
struct A
{
A(int x) : b{ x } {}
struct B
{
int x;
} b;
};
int main()
{
A a( 10 );
}
then the code will compile successfully because b
will be initialized as an aggregate.
Now, if in the constructor we change the type of the parameter from int
to A::B
, then the situation will change.
This program will compile successfully
struct A
{
struct B;
A(B x) : b( x ) {}
struct B
{
int x;
} b;
};
int main()
{
A::B b = { 10 };
A a( b );
}
because structs that are aggregates have a copy constructor created implicitly by the compiler.
The situation is different when the data member that represents the aggregate is an array. As with the structure, this program will not compile
struct A
{
struct B;
A(int x) : b( x ) {}
int b[1];
};
int main()
{
int b = 10;
A a( b );
}
since there is no conversion from integer type to array.
However, if you replace the parameter with an array, it does not matter if it is a reference or not, as shown below
struct A
{
struct B;
A(int x[1]) : b( x ) {}
int b[1];
};
int main()
{
int b[1] = { 10 };
A a( b );
}
or
struct A
{
struct B;
A(int ( &x )[1]) : b( x ) {}
int b[1];
};
int main()
{
int b[1] = { 10 };
A a( b );
}
then the program will not compile, since in the first case there is no conversion from a pointer to an array, and in the second case, when the parameter is declared as a reference, arrays do not have a copy constructor.
To initialize a data member that is an array, you can use the following entry
struct A
{
struct B;
A(int x) : b{ x } {}
int b[1];
};
int main()
{
int b = 10;
A a( b );
}
This program will compile successfully. However, you cannot enclose an entry with curly braces in parentheses, as shown below.
struct A
{
struct B;
A(int x) : b({ x }) {}
int b[1];
};
int main()
{
int b = 10;
A a( b );
}
The compiler will issue an error because, again, there is no copy constructor for arrays. However, for structures, such an initialization record will be successfully accepted by the compiler, since structures, like aggregates, have a copy constructor implicitly declared by the compiler.
struct A
{
A(int x) : b({ x }) {}
struct B
{
int x;
} b;
};
int main()
{
int b = 10;
A a( b );
}
This program will compile successfully.
For arithmetic types, initialization with curly braces does not allow "narrowing" the value, that is, using as an initializer a value that could potentially not fit in the object being initialized. So the following program will not compile
struct A
{
A(int x) : b{ x } {}
short b;
};
int main()
{
int b = 10;
A a( b );
}
The reason for the error will be that an object of type short
is not able to accommodate all the values of an object of type int
, that is, there will be a "narrowing" of the initialization value.
However, if you replace the curly braces with parentheses, the program will compile successfully.
struct A
{
A(int x) : b( x ) {}
short b;
};
int main()
{
int b = 10;
A a( b );
}
When the class member being initialized is a user-defined type, then constructors come into play, which have a parameter of type std::initializer_list
For example, in the program below, when there is no such constructor, you can initialize a class member as b( x )
, or as b{ x }
, or even as b( { x } )
struct A
{
A(int x) : b({ x }) {}
struct B
{
B(int x)
{
std::cout << "B( int )" << std::endl;
}
} b;
};
int main()
{
int b = 10;
A a(b);
}
However, if the class has a constructor with a parameter of type std::initializer list
, then it will be called for initializations like b{ x }
and b( { x } )
. And to initialize the form b( x )
, another constructor will be called.
For example, for this program
struct A
{
A(int x) : b( x ) {}
struct B
{
B(int x)
{
std::cout << "B( int )" << std::endl;
}
B(std::initializer_list<int>)
{
std::cout << "B( std::initializer_list<int> )" << std::endl;
}
} b;
};
int main()
{
int b = 10;
A a(b);
}
message will be displayed
B( int )
But for this program
struct A
{
A(int x) : b{ x } {}
struct B
{
B(int x)
{
std::cout << "B( int )" << std::endl;
}
B(std::initializer_list<int>)
{
std::cout << "B( std::initializer_list<int> )" << std::endl;
}
} b;
};
int main()
{
int b = 10;
A a(b);
}
a message will be displayed
B( std::initializer_list<int> )
In general, this topic is quite extensive.
I wrote about some quirks of initialization on my website at the end of the topic The joke is a lie, but there is a hint in it, a lesson for good fellows.