brace enclosed initializer list

I have class Phenotype with the following constructor:

I can create a Phenotype like this:

But I get an error when I try to create one like this:

The error seems to indicate that there is a way to define a constructor which takes a brace-enclosed initializer list. Does anyone know how this might be done?

3 Answers 3

It can only be done for aggregates (arrays and certain classes. Contrary to popular belief, this works for many nonpods too). Writing a constructor that takes them is not possible.

Since you tagged it as «C++0x», then this is possible though. The magic words is «initializer-list constructor». This goes like

However, such initialization will default construct the array and then use the assignment operator. If you aim for speed and safety (you get compile time errors for too many initializers!), you can also use an ordinary constructor with a variadic template.

This can be more generic than needed though (often an initializer_list completely suffices, especially for plain integers). It benefits from perfect forwarding, so that an rvalue argument can be move constructed into an array element

It’s a hard choice!

Edit Correction, the last one works too, as we didn’t make the constructor explicit , so it can use the copy constructor of Phenotype , constructing a temporary Phenotype object and copy it over to p3 . But that’s not what we really would want the calls to be 🙂

Обновл. 24 Июн 2019 |

В этом уроке мы рассмотрим, что такое список инициализации std::initializer_list, его использование и нюансы.

Списки инициализации

Для инициализации этого массива мы можем использовать список инициализации:

Это также работает и с динамически выделенными массивами:

В предыдущем уроке мы рассматривали контейнерные классы на примере класса-массива целых чисел ArrayInt:

Что произойдёт, если мы попытаемся использовать список инициализации с этим контейнерным классом?

Этот код не скомпилируется, так как класс ArrayInt не имеет конструктора, который бы знал, что делать со списком инициализации. Итого, каждый элемент нужно инициализировать в индивидуальном порядке:

Как-то не очень, правда?

До C++11 списки инициализации могли использоваться только со статическими или динамически выделенными массивами. Однако в C++11 появилось решение этой проблемы.

Инициализация классов через std::initializer_list

Когда компилятор C++11 видит список инициализации, то он автоматически конвертирует его в объект типа std::initializer_list. Поэтому, если мы создадим конструктор, который принимает в качестве параметра std::initializer_list, мы сможем создавать объекты, используя список инициализации в качестве входных данных.

std::initializer_list находится в заголовочном файле initializer_list.

Есть несколько вещей, которые нужно знать о std::initializer_list. Так же, как и с std::array и std::vector, вы должны указать в угловых скобках std::initializer_list какой тип данных будет использоваться. По этой причине вы никогда не увидите пустой std::initializer_list. Вместо этого вы увидите что-то вроде std::initializer_list или std::initializer_list .

Во-вторых, std::initializer_list имеет функцию (не совсем правильно названную) size(), которая возвращает количество элементов списка. Это полезно, когда нам нужно знать длину получаемого списка.

Обновим наш класс-массив ArrayInt, добавив конструктор, который принимает std::initializer_list:

Результат выполнения программы выше:

Работает! Теперь рассмотрим это всё подробнее.

Вот наш конструктор, который принимает std::initializer_list :

Строка №1: Как мы уже говорили, обязательно нужно указывать используемый тип данных в угловых скобках std :: initializer_list . В этом случае, поскольку это ArrayInt, то ожидается, что список будет заполнен значениями типа int. Обратите внимание, мы передаём список по константной ссылке, дабы избежать создания копии std::initializer_list при передаче в конструктор.

Строка №2: Мы делегируем выделение памяти для начального объекта ArrayInt, в который будем выполнять копирование элементов, другому конструктору, используя концепцию делегирующих конструкторов, чтобы сократить лишний код. Этот другой конструктор должен знать длину выделяемого объекта, поэтому мы передаём ему list.size() , который указывает на количество элементов списка.

В теле нашего конструктора мы выполняем копирование элементов из списка инициализации в класс ArrayInt. По каким-то необъяснимым причинам std::initializer_list не предоставляет доступ к своим элементам через оператор индексации []. Об этом много говорили, но официального решения так и не предоставили.

Тем не менее, есть способы это обойти. Самый простой — использовать цикл foreach. Цикл foreach перебирает каждый элемент списка и мы, таким образом, копируем каждый элемент в наш внутренний массив.

Присваивание значений и std::initializer_list

Вы также можете использовать std::initializer_list для присваивания новых значений классу, перегрузив оператор присваивания для получения std::initializer_list в качестве параметра. Пример того, как это делается будет в тестовом задании ниже.

Обратите внимание, если вы создаёте конструктор, который принимает std::initializer_list, то вы должны проследить, чтобы хоть одно из следующих действий было выполнено:

Перегрузка оператора присваивания.

Наличие явного конструктора (с ключевым словом explicit), чтобы он не мог использоваться для неявных преобразований.

Почему? Рассмотрим класс выше (который не имеет перегрузки оператора присваивания или копирующего присваивания) со следующим стейтментом:

Во-первых, компилятор видит, что функции присваивания, которая принимает std::initializer_list в качестве параметра, не существует. Затем он ищет другие функции, которые он мог бы использовать, и находит неявно предоставленный копирующий оператор присваивания. Однако эта функция может использоваться, только если она сможет преобразовать список инициализации в ArrayInt, а поскольку у нас есть конструктор, который принимает std::initializer_list и он не помечен как explicit, то компилятор будет использовать этот конструктор для преобразования списка инициализации во временный ArrayInt. Затем вызовется неявный оператор присваивания, который используется в конструкторе и который будет выполнять поверхностное копирование временного объекта ArrayInt в наш объект array .

И тогда m_data временного объекта ArrayInt и m_data объекта array будут указывать на один и тот же адрес (из-за поверхностного копирования). Вы уже можете догадаться, до чего это приведёт.

В конце стейтмента присваивания временный ArrayInt уничтожается. Вызывается деструктор, который удаляет временный m_data класса ArrayInt. Это оставляет наш объект array с висячим указателем m_data . Когда мы попытаемся использовать m_data объекта array для любых целей (в том числе, когда массив будет выходить из области видимости и деструктору нужно будет уничтожить m_data ), то мы получим неопределённые результаты (или сбой).

Заключение

Реализация конструктора, который принимает std::initializer_list в качестве параметра (используется передача по ссылке для предотвращения копирования), позволяет нам использовать список инициализации с нашими пользовательскими классами. Мы также можем использовать std::initializer_list для реализации других функций, которым необходим список инициализации (например для перегрузки оператора присваивания).

Используя класс ArrayInt выше, реализуйте перегрузку оператора присваивания, который будет принимать список инициализации. Следующий код:

I’m at a bit of a loss here. Error is on the emplace_back line and no idea how to resolve it. Any help or hints would be appreciated, thanks.

gcc version 4.8.2

The problem is with the struct initialization = <0>and with emplace_back .

2 Answers 2

emplace_back() uses template argument deduction to determine the types of the elements passed to the function. A brace enclosed initializer list is not an expression and doesn’t have type and therefore cannot be deduced by the template. You have to explicitly call the constructor here:

There are two issues here :

Trying to init a object of type T like this T <. >is referred to as aggregate initialization. Under some conditions, there is a default behaviour specified for it, even if you don’t have a constructor which accepts a initializer_list . In C++11 , you are not allowed to provide non-default constructors or in-class initializers. So, given this definition

you cannot write item_t t<1,2,3>; .

That, however, isn’t your problem. The reason your code fails is that emplace_back tries to forward the arguments to a constructor of the vector s underlying type. In your case, there isn’t a match. Note that nice a braced-init list isn’t equivalent to an initializer_list in this context, you cannot solve this problem by adding an initializer_list constructor and will have to help the compiler out some other way.

Оцените статью