image0

Prior C++11, curly braces could be used to initialize POD-compliant structures or classes only. In C++11, the use of curly braces has been extended. This improvement has been done partly for the new std::initializer_list object, that is useful when constructing sequence objects, for example a std::vector:

// Valid since C++11.
std::vector<int> values { 1, 2, 3, 4 };

Basically, the syntax above uses the constructor of std::vector that takes an initialization list, and as such constructs a std::initializer_list object, that is passed to the appropriate constructor of std::vector (if the object to craft has no constructor that accepts this kind of list, no list is created, and the compiler will try to match the arguments within the curly braces with an existing constructor). Also, this type is automatically constructed when combining curly braces with the keyword auto; for example:

// Creates an initializer list.
auto values { 1, 2, 3, 4 };

This is easily verified:

// Ensure that values is of type std::initializer_list<int>.
static_assert(
    std::is_same<decltype(values), std::initializer_list<int>>::value,
    "values is supposed to be an instance of std::initializer_list<int>!");

Another advantage of std::initializer_list is that it is a lightweight object. Here is the benchmarking function taking measures of both size and time for a container that can be constructed with curly braces:

template <typename Container>
void loop (std::size_t count) {
    using clock_type = std::chrono::high_resolution_clock;
    using period_type = std::chrono::nanoseconds;

    std::size_t total_size = 0;

    clock_type::time_point begin = clock_type::now();

    for (std::size_t i = 0; i < count; ++i) {
       Container values { 1, 2, 3, 4 };

       total_size += sizeof(values);
    }

    clock_type::time_point end = clock_type::now();

    period_type elapsed = std::chrono::duration_cast<period_type>(end - begin);

    std::cout << "total size:\t" << total_size
              << "\telapsed: " << elapsed.count() << std::endl;
}

Using it for both a std::vector and a std::initializer_list is trivial:

static constexpr std::size_t million = 1000000;

std::cout << "*** vector" << std::endl;
loop<std::vector<int>>(million);

std::cout << "*** initializer_list" << std::endl;
loop<std::initializer_list<int>>(million);

Here are the results:

*** vector
total size:     24000000        elapsed: 165951000
*** initializer_list
total size:     16000000        elapsed: 5051000

The std::vector version is 50% slower and takes about three times more space that the std::initializer_list version. An interesting use of std::initializer_list can be to check that a value is found amongst multiple ones. This can already be done with algorithms like std::any_of, but they require iterators. However, there are cases where it would be easier to directly check a value against multiple ones, without having to store them beforehand. For example, when crafting an item model in Qt, it is pretty usual to check for roles to handle. As such, overriding a method from QAbstractItemModel often begins with something like this:

if ((role == Qt::DisplayRole) or (role == Qt::DecorationRole) or (role == Qt::SizeHintRole)) {
    // ...
}

While it is pretty simple as-is, it would be more readable, and even simpler to write:

if (any_of(role, { Qt::DisplayRole, Qt::DecorationRole, Qt::SizeHintRole })) {
    // ...
}

template <typename Type>
bool any_of(const Type& candidate, const std::initializer_list<Type>& values) {
    auto compare = std::bind(std::equal_to<Type>(), candidate, std::placeholders::_1);
    return std::any_of(values.begin(), values.end(), compare);
}

C++11 std::initializer_list allows better design by joining together elegant code and efficiency.


Comments

comments powered by Disqus