Initializer_list как аргумент для ссылочного параметра массива в контексте не-шаблона
Мой вопрос касается этого очень простого и короткого кода, в котором производится попытка перегрузки между двумя функциями без шаблона, принимающими параметр ссылки на массив. Вопрос был опубликован в другом месте, но в контексте шаблона вывода. Здесь код:
#include <iostream>
void foo ( const int (&x) [3] ) { std::cout << "3\n"; }
void foo ( const int (&x) [2] ) { std::cout << "2\n"; }
int main()
{
foo({1,2,3});
}
g++ 4.8.3 компилирует этот код, выбирая первую функцию как (я полагаю) ТОЛЬКО жизнеспособную,
в то время как clang 3.4 не компилирует его, говоря, что вызов foo неоднозначен (почему?).
Какой компилятор делает правильные вещи?
clang не компилирует код, даже удаляя вторую перегрузку: кажется, что initializer_list просто не принят для инициализации ссылки массива.
Является ли это ошибкой?
Ответы
Ответ 1
Я думаю, что хотя поведение GCC более полезно, это поведение clang корректно для С++ 11:
13.3.3.1.5 Последовательность инициализации списка [over.ics.list]
2 Если тип параметра std::initializer_list<X>
или "array of X
", и все элементы списка инициализатора могут быть неявно преобразованы в X
, неявная последовательность преобразования является наихудшим преобразованием, необходимым для преобразования элемента списка X
.
Эта последовательность преобразования не обращает внимания на длину массива. Обе функции перегрузки дают неявную последовательность преобразований, которая является преобразованием идентичности: оба берут ссылку на массив из int
, и каждый элемент в аргументе функции является int
.
Разрешение перегрузки затем видит два преобразования идентичности, и хотя в стандарте есть несколько исключений для разрешения конфликтов при преобразованиях равного ранга, никто не обращает внимания на длину массива:
13.3.3.2 Ранжирование неявных последовательностей преобразования [over.ics.rank]
3 Две неявные последовательности преобразования одной и той же формы являются неотличимыми последовательностями преобразования, если не применяется одно из следующих правил:
а затем список, который вообще не упоминает массивы.
Джонатан Вакели указывает, что с тех пор это изменилось. Ваш вопрос именно то, что вызвало это изменение, и соответствующий DR #1307. В С++ 14 ваш код действителен, но не в С++ 11.
Ответ 2
DR 1232 изменил С++ 11, чтобы разрешить вызывающие функции с параметрами reference-to-array из списка инициализаторов. (Изменения в рабочем документе показаны в N3262.) Все тестируемые компиляторы реализуют это правило.
DR1307 затем снова изменил ту же формулировку, чтобы устранить двусмысленность, которую вы обнаружили. Ваш код принят компиляторами GCC и EDG, поэтому я полагаю, что Clang еще не реализует DR.
Интересно отметить, что даже после того, как DR был разрешен, foo({1, 2})
по-прежнему неоднозначен и отклонен всеми компиляторами. Причина в том, что {1, 2}
может привязываться к параметру const int(&)[2]
(очевидно), но он также может привязываться к параметру const int(&)[3]
, потому что int
по умолчанию является конструктивным, поэтому он означает то же самое, что и {1, 2, int()}
т.е. {1, 2, 0}