Code Yarns ‍👨‍💻
Tech BlogPersonal Blog

Uniform initialization in C++

📅 2015-Nov-02 ⬩ ✍️ Ashwin Nanjappa ⬩ 🏷️ cpp, uniform initialization ⬩ 📚 Archive

Initializing in C++98 used to be a pain:

C++11 introduced an uniform initialization syntax using {} that makes writing and reading C++ a joy! In my opinion, this is a feature that will make everyone's code easier to write and read. Though it does have a few rough edges (since C++ has to support all previous syntaxes), it almost always works in a way that is most intuitive to the viewer.

It is called uniform initialization because it is applicable in every place where you initialize anything. That includes: variables, constants, objects, in the initializer list of a constructor and even in the member declaration of a struct or class. Everywhere!

I am sharing a code sample below that enumerates the various ways to apply this syntax:

#include <iostream>
#include <stack>
#include <unordered_set>
#include <vector>

class Point
{
    public:
    int x;
    int y;
    int z {45}; // Look ma! Default init of member!
};

class Line
{
    public:

    // In initializer list
    Line() : p {}, q {10}
    {}

    Line(int x, int y) : p {x}, q{y}
    {}

    int p;
    int q;
    int r {99}; // Look ma! Default init outside ctor!
};

int main()
{
    // Init with default values of builtin types
    {
        char        c {}; // c = 0
        int         i {}; // i = 0
        float       f {}; // f = 0.0f
        double      d {}; // d = 0.0
        bool        b {}; // b = false
        std::string s {}; // s = ""
        int*        p {}; // p = nullptr
    }

    // Init with specific values
    {
        char        c {'x'};   // c = 'x'
        int         i {99};    // i = 99
        float       f {3.14f}; // f = 3.14f
        double      d {1.23};  // d = 1.23
        bool        b {true};  // b = true
        std::string s {"cat"}; // s = "cat"
        int*        p {&i};    // p = &i
    }

    // Narrowing convertions not allowed
    // This can be based on type (float-to-int) or actual value (int-to-char)
    {
        int i {3};    // OK
        int j {3.0};  // Error!
        int k {3.14}; // Error!
    }

    // Init array values
    int i_arr[5] {};                // [0,   0, 0, 0, 0]
    int j_arr[] {99, 10, 3};        // [99, 10, 3]
    int k_arr[5] {99, 10, 3};       // [99, 10, 3, 0, 0]
    int m_arr[5] {99, 10, 3, 5, 7}; // [99, 10, 3, 5, 7]

    // Init struct/class members (with no ctor)
    // See above for definition of Point class
    Point p0 {};           // .x =  0, .y =  0, .z = 45
    Point p1 {99};         // .x = 99, .y =  0, .z = 45
    Point p2 {99, 15, 24}; // .x = 99, .y = 15, .z = 24

    // Init struct/class (with ctor)
    Line s0 {};           // .p = 0,  .q = 10, .r = 99
    Line s2 {10, 11};     // .p = 10, .q = 11, .r = 99
    Line s1 {10};         // Error! No matching ctor found!
    Line s3 {10, 11, 13}; // Error! No matching ctor found!

    /**
     * Init STL vectors
     */

    // Init vectors
    std::vector<int> i_vec_0 {5};          // [5]
    std::vector<int> i_vec_1 {5, 2};       // [5, 2]
    std::vector<int> i_vec_2 {2, 3, 4, 5}; // [2, 3, 4, 5]
    std::vector<int> i_vec_3 (5);          // [0, 0, 0, 0, 0]

    // A matching constructor is chosen over aggregation
    // Note what happens on passing int
    std::vector<std::string> s_vec_0 {};                    // []
    std::vector<std::string> s_vec_1 {"cat", "rat", "mat"}; // ["cat", "rat", "mat"]
    std::vector<std::string> s_vec_2 {4};                   // ["", "", "", ""]
    std::vector<std::string> s_vec_3 {4, "ba"};             // ["ba", "ba", "ba", "ba"]

    /**
     * Other STL containers
     */

    std::unordered_set<int> i_set {99, 32, 45, 32}; // [99, 32, 45]

    return 0;
}

Tried with: GCC 5.1 and Ubuntu 14.04