C++ 11 – Uniform Initializations
Before C++ 11, we have different ways of initializations for each object and data type. It is confusing for beginners.
As an example, consider these various ways of initializations available. There is a new way for each type of object, data type, and scenario.
//Built-in types
int a = 42; // Copy initialization
int b(42); // Direct initialization
// User defined types
MyClass obj1(10, 2.7); // Direct initialization
MyClass obj2 = MyClass(10, 2.7); // Copy initialization (less efficient)
// STL Containers
std::vector<int> vec1;
vec1.push_back(1);
vec1.push_back(2);
vec1.push_back(3);
struct Point {
int x;
int y;
};
Point p1 = {3, 4}; // Aggregate initialization
C++ 11 introduced uniform initialization. It means that all initializations can be made consistently using braces – no need to use different ways and remember different methods for each case.
The uniform initialization improves the readability as well as consistency of the code. Also, narrowing conversions are not allowed. It helped prevent unintended type conversions that may lead to data loss.
Give below is the usage of the uniform initialization using braces for different scenarios. The sample program can be downloaded at https://github.com/codeversionmaster/cplusplus/blob/cplusplus17/uniform_initialization.cpp.
Note: Although uniform initialization is there in C++ 11 onwards, this program has some extra containers and code snippets that are present only from C++ 14 or C++ 17 to show brace initialization usage in those cases as well.
These are the headers and declaration of the Sample class and Point structure.
#include <iostream>
#include <vector>
#include <map>
#include <tuple>
#include <optional>
#include <variant>
#include <tuple>
#include <memory>
using namespace std;
class Sample {
public:
Sample(int x, double y) : x_(x), y_(y) {}
void print() { cout << "Sample: x=" << x_ << ", y=" << y_ << endl; }
private:
int x_; double y_;
};
struct Point { int x; int y; };
This snippet is for uniform initialization while the function accepts arguments.
// Function that takes an aggregate type as an argument using brace initialization
void print_point(const Point& p) {
cout << "Point: x=" << p.x << ", y=" << p.y << endl;
}
The function create_point uses uniform initialization while returning the parameters.
// Function that returns an aggregate type using brace initialization
Point create_point(int x, int y) {
return {x, y};
}
This is the start of the program.
int main() {
The brace initialization for built-in types such as int and double is as shown.
// Built-in types
int a{42};
double b{3.14};
cout << "a: " << a << ", b: " << b << endl;
This is how initialization can be done using braces in the case of classes and other user-defined data types.
// User-defined types
Sample obj{10, 2.7};
obj.print();
Containers such as vectors also can use brace initialization like any other data type, as shown.
// STL containers
vector<int> vec{1, 2, 3, 4, 5};
cout << "Vector: ";
for (const auto& v : vec) {
cout << v << " ";
}
cout << endl;
This is to show a map container using braces for initializing the values.
map<string, int> mapping{
{"one", 1},
{"two", 2},
{"three", 3},
};
cout << "Map: ";
for (const auto& [key, value] : mapping) {
cout << key << "=" << value << " ";
}
cout << endl;
Here, aggregate initialization is done on the structure Point.
// Aggregate initialization
Point p1{3, 4};
print_point(p1);
This portion of the code demonstrates function argument and return value initialization.
// Function argument and return value initialization
Point p2 = create_point(7, 9);
print_point(p2);
We can use braces on an initializer list, as shown.
// Initializer list
auto sum = [](initializer_list<int> numbers) {
int total = 0;
for (const auto& num : numbers) {
total += num;
}
return total;
};
int total = sum({1, 2, 3, 4, 5});
cout << "Sum: " << total << endl;
This snippet is for brace initialization on arrays.
// Array initialization
int arr[]{1, 2, 3, 4, 5};
cout << "Array: ";
for (const auto& v : arr) {
cout << v << " ";
}
cout << endl;
The code is for pair and tuple initialization using braces.
// pair and tuple initialization
pair<int, string> my_pair{1, "one"};
cout << "Pair: " << my_pair.first << ", " << my_pair.second << endl;
tuple<int, double, string> my_tuple{1, 3.14, "hello"};
cout << "Tuple: " << get<0>(my_tuple) << ", " << get<1>(my_tuple) << ", " << get<2>(my_tuple) << endl;
Similarly, variant, optional, unique_ptr, shared_ptr, and lambda capture can use brace initialization in a consistent manner.
// variant initialization with braces
variant<int, double, string> my_variant{42};
// brace initialization in std::optional
optional<int> my_optional{42};
// usage of braces in unique_ptr and shared_ptr initializations
unique_ptr<int> my_unique_ptr{new int{42}};
shared_ptr<int> my_shared_ptr{new int{42}};
// lambda captures using brace initialization
int x = 42;
auto my_lambda = [x{move(x)}]() { return x; };
This is the end of the program.
return 0;
}
You can compile and run the program as shown.
$ g++ uniform_initialization.cpp -o uniform_initialization
$ ./uniform_initialization
a: 42, b: 3.14
Sample: x=10, y=2.7
Vector: 1 2 3 4 5
Map: one=1 three=3 two=2
Point: x=3, y=4
Point: x=7, y=9
Sum: 15
Array: 1 2 3 4 5
Pair: 1, one
Tuple: 1, 3.14, hello