Brace Initialization: A Mess

Explicit Constructor And Brace Initialization

  • Any explicit constructor can be invoked the “new” brace initialization syntax

  • … unless there is a std::initializer_list constructor

#include <gtest/gtest.h>

struct point
{
    point(int x, int y)        // <--- explicit ctor, 2 parameters
    : x{x}, y{y} {}

    int x;
    int y;
};

TEST(brace_initialization_suite, basic)
{
    point p1(1,2);             // <--- explicitly using explicit ctor
    point p2{3,4};             // <--- implicitly using explicit ctor (no initializer_list ctor found)

    ASSERT_EQ(p1.x, 1);
    ASSERT_EQ(p1.y, 2);
    ASSERT_EQ(p2.x, 3);
    ASSERT_EQ(p2.y, 4);
}

Ambiguity: Explicit And std::initializer_list Constructor

  • What if there are two constructors which would satisfy calling sequence?

  • ⟶ be explicit

#include <gtest/gtest.h>

class point
{
public:
    point(int x, int y)
    : x{x}, y{y} {}

    int x;
    int y;
};

struct pointcloud
{
    pointcloud(point p1, point p2)                     // <--- explicit ctor
    {
        points.push_back(p1);
        points.push_back(p2);
        explicit_ctor_called = true;
        initlist_ctor_called = false;
    }
    pointcloud(std::initializer_list<point> points)    // <--- initializer_list ctor
    : points(points)
    {
        explicit_ctor_called = false;
        initlist_ctor_called = true;
    }

    std::vector<point> points;

    bool initlist_ctor_called;
    bool explicit_ctor_called;
};

TEST(brace_initialization_suite, explicit_ctor)
{
    point p1{1,2};
    point p2{3,4};

    pointcloud cloud(p1, p2);                          // <--- using explicit ctor

    ASSERT_TRUE(cloud.explicit_ctor_called);
    ASSERT_EQ(cloud.points.size(), 2);
}

TEST(brace_initialization_suite, initlist_ctor)
{
    point p1{1,2};
    point p2{3,4};

    pointcloud cloud{p1, p2};                          // <--- initializer_list ctor

    ASSERT_EQ(cloud.points.size(), 2);
    ASSERT_TRUE(cloud.initlist_ctor_called);
}

Rule

  • Use brace initialization, preferably

  • Unless something does not work or compile 🐷

Realistic Example: std::vector: Two int (Explicit)

  • std::vector<int> has one explicit constructors which takes two int parameters

    • One is the number of elements to create, one is their value

  • Using the explicit constructor, explicitly, by writing gool old function-call-style initialization

#include <vector>
#include <iostream>

int main()
{
    std::vector<int> ints(3, 42);
    for (int i: ints) std::cout << i << '\n';

    return 0;
}
$ ./code/c++11-brace-initialization-vector-explicit
42
42
42

Realistic Example: std::vector: Two int (std::initializer_list)

  • std::vector<int> has one another constructor which takes a std::initializer_list<int>

  • … of arbitrary size - even 2 is ok

  • ⟶ must use brace initialization to disabiguate

#include <vector>
#include <iostream>

int main()
{
    std::vector<int> ints{3, 42};
    for (int i: ints) std::cout << i << '\n';

    return 0;
}
$ ./code/c++11-brace-initialization-vector-initializer-list
3
42

Nicolai Josuttis, Complaining About C++