Рассмотрим следующую программу:
struct X
{
X(int, int) { }
X(X&&) { }
};
int main()
{
X x( {0, 1} ); // Doesn't compile on ICC 13.0.1, compiles on
// Clang 3.2, GCC 4.7.2, and GCC 4.8.0 beta.
}
При компиляции с GCC 4.7.2, GCC 4.8.0 и Clang 3.2 эта программа выполняет следующие действия (*):
X
проходящие значения 0
а также 1
затем конструктору;X
из этого временного.Вместо этого ICC 13.0.1 не компилируется.
ВОПРОС № 1: Кто прав?
(*) На самом деле создание временного объекта и вызов конструктора перемещения исключаются, но компилируются с -fno-elide-constructors
опция и добавление некоторых распечаток в конструкторы показывает, что это то, что происходит.
Теперь рассмотрим следующее, небольшое изменение вышеуказанной программы, где равномерная инициализация используется для прямой инициализации x
:
int main()
{
X x{ {0, 1} }; // ERROR! Doesn't compile.
// ^........^
}
Я бы не ожидал, что использование скобок вместо скобок что-то здесь изменит, но это как-то так: эта программа не компилируется любой из компиляторов, на которых я его тестировал (Clang 3.2, GCC 4.7.2, GCC 4.8.0 beta и ICC 13.0.1).
ВОПРОС № 2: Почему?
Это просто ошибка во всех компиляторах. §8.5.4 / 3 говорит,
Инициализация списка объекта или ссылки типа T определяется следующим образом:
— Если список инициализаторов не имеет элементов и T является типом класса с конструктором по умолчанию, объект
значение инициализации.— В противном случае, если T является агрегатом, выполняется агрегатная инициализация (8.5.1).
— В противном случае, если T является специализацией std :: initializer_list, объект initializer_list создается, как описано ниже, и используется для инициализации объекта…
— В противном случае, если T является типом класса, учитываются конструкторы. Применимые конструкторы перечисляются, и лучший выбирается через разрешение перегрузки (13.3, 13.3.1.7). Если для преобразования какого-либо из аргументов требуется сужающее преобразование (см. Ниже), программа является некорректной.
— В противном случае, если T является ссылочным типом, prvalue, временный для типа, на который ссылается T, инициализируется списком, и ссылка привязывается к этому временному объекту.
— В противном случае, если список инициализатора имеет один элемент, объект или ссылка инициализируются из этого элемента; если для преобразования элемента в T требуется сужающее преобразование (см. ниже), программа является некорректной.
…
Есть довольно много случаев. Обратите внимание, что вы на самом деле инициализируете список prvalue временного объекта, привязанного к rvalue ссылке.
Что касается GCC, я думаю, что он пытается применить последний элемент, упомянутый выше, для списка одноэлементного инициализатора. Если я изменю подпись конструктора на X(X&&, int = 3)
затем инициализатор { {0, 1} }
не удается, но { {0, 1}, 3 }
преуспевает. Единственный элемент должен быть успешным, потому что, поскольку этот элемент является списком фигурных скобок инициализации, и я считаю, что регистр должен допускать дополнительные заключающие в скобки символы, аналогичные скобкам. Но неудача аналогична другим недостаткам GCC с опорой на скобки.
У меня сложилось впечатление, что проблемы возникают, когда компилятор пытается обработать список как объект с типом, а это не так. Трудно преобразовать это обратно во что-то вроде списка аргументов в скобках.
Более конкретно рассмотрев сообщения об ошибках (спасибо за ссылку LWS),
ICC настаивает на том, что ожидает выражения. Это неверно, потому что в соответствии с базовой грамматикой, фигурные скобки-списки инициализации могут включать в себя другие фигурные скобки-списки инициализации, а не только выражения.
Clang говорит msgstr «конструктор-кандидат недопустим: невозможно преобразовать аргумент списка инициализатора в ‘X'» но это работает, если преобразование явное, используя X x{ X{0, 0 } };
, Это не имеет смысла. Это не конверсия, потому что у списка нет типа для конвертации. Это инициализация списка.
GCC говорит «нет известного преобразования для аргумента 1 из » в ‘X&&«» предполагая, что это даже не дошло до определения временного привязки к ссылке. Как и в случае с Clang, он, похоже, пытается поддельное преобразование и указывает X{0,0}
исправляет это.
Других решений пока нет …