Странный случай разрешения перегрузки в C ++ 11 - PullRequest
9 голосов
/ 24 октября 2011

Сегодня я столкнулся с довольно странным случаем разрешения перегрузки. Я сократил это до следующего:

struct S
{
    S(int, int = 0);
};

class C
{
public:
    template <typename... Args>
    C(S, Args... args);

    C(const C&) = delete;
};

int main()
{
    C c({1, 2});
}

Я полностью ожидал, что C c({1, 2}) будет соответствовать первому конструктору C, с числом аргументов переменной равным нулю, а {1, 2} рассматривается как конструкция списка инициализатора объекта S.

Однако я получаю ошибку компилятора, которая указывает, что вместо этого он соответствует конструктору удаленных копий C!

test.cpp: In function 'int main()':
test.cpp:17:15: error: use of deleted function 'C(const C &)'
test.cpp:12:5: error: declared here

Я могу как-то увидеть, как это может работать - {1, 2} может быть истолковано как допустимый инициализатор для C, с 1, являющимся инициализатором для S (который неявно может быть создан из int, потому что второй аргумент его конструктора имеет значение по умолчанию), а 2 является аргументом с переменным числом аргументов ... но я не понимаю, почему это было бы лучше, особенно если учесть, что рассматриваемый конструктор копирования удален.

Может, кто-нибудь объяснит правила разрешения перегрузки, которые здесь используются, и скажите, существует ли обходной путь, не включающий упоминание имени S в вызове конструктора?

EDIT : Поскольку кто-то упоминал, что сниппет компилируется с другим компилятором, я должен уточнить, что я получил вышеупомянутую ошибку с GCC 4.6.1.

РЕДАКТИРОВАТЬ 2 : я еще больше упростил фрагмент, чтобы получить еще более тревожный сбой:

struct S
{
    S(int, int = 0);
};

struct C
{
    C(S);
};

int main()
{
    C c({1});
}

Ошибка:

test.cpp: In function 'int main()':
test.cpp:13:12: error: call of overloaded 'C(<brace-enclosed initializer list>)' is ambiguous
test.cpp:13:12: note: candidates are:
test.cpp:8:5: note: C::C(S)
test.cpp:6:8: note: constexpr C::C(const C&)
test.cpp:6:8: note: constexpr C::C(C&&)

И в этот раз GCC 4.5.1 также выдает ту же ошибку (за исключением constexpr s и конструктора перемещения, который он не генерирует неявно).

Я нахожу это очень Трудно поверить, что именно это и было задумано дизайнерами языка ...

Ответы [ 2 ]

6 голосов
/ 25 октября 2011

Для C c({1, 2}); у вас есть два конструктора, которые можно использовать.Таким образом, разрешение перегрузки имеет место и смотрит, какую функцию взять

C(S, Args...)
C(const C&)

Args будет выведено в ноль, как вы выяснили.Таким образом, компилятор сравнивает построение S с созданием временного C из {1, 2}.Построение S из {1, 2} является прямым и принимает ваш объявленный конструктор S.Построение C из {1, 2} также является простым и принимает ваш шаблон конструктора (конструктор копирования нежизнеспособен, потому что он имеет только один параметр, но передаются два аргумента - 1 и 2).Эти две последовательности преобразования не сопоставимы.Таким образом, два конструктора были бы неоднозначными, если бы не тот факт, что первым является шаблон.Поэтому GCC предпочтет не шаблон, выбрав конструктор удаленных копий, и даст вам диагностику.

Теперь для вашего C c({1}); тестового примера можно использовать три конструктора

C(S)
C(C const&)
C(C &&)

Для двух последних компилятор предпочтет третий, потому что он связывает r-значение с r-значением.Но если вы рассмотрите C(S) против C(C&&), вы не найдете победителя между двумя типами параметров, потому что для C(S) вы можете построить S из {1} и для C(C&&) вы можете инициализировать C временно из {1} путем взятия конструктора C(S) (Стандарт прямо запрещает использование пользовательских преобразований для параметра конструктора перемещения или копирования для инициализации объекта класса C из {...},поскольку это может привести к нежелательным неясностям, именно поэтому преобразование 1 в C&& здесь не рассматривается, а только преобразование из 1 в S).Но в этот раз, в отличие от вашего первого тестового примера, ни один конструктор не является шаблоном, так что в итоге вы получите двусмысленность.

Именно так все и должно работать.Инициализация в C ++ - это странно, поэтому, к сожалению, получить все «интуитивно» для всех невозможно.Даже простой пример, как указано выше, быстро усложняется.Когда я написал этот ответ и через час я случайно посмотрел на него, то заметил, что что-то упустил и должен был исправить ответ.

4 голосов
/ 24 октября 2011

Возможно, вы правы в том, почему он может создать C из этого списка инициализатора. ideone успешно компилирует ваш пример кода, и оба компилятора не могут быть правильными. Предполагая, что создание копии допустимо, однако ...

Таким образом, с точки зрения компилятора, у него есть два варианта: создать новый S{1,2} и использовать шаблонный конструктор, или создать новый C{1,2} и использовать конструктор копирования. Как правило, не шаблонные функции предпочтительнее шаблонных, поэтому выбирается конструктор копирования. Затем проверяет, может ли функция быть вызвана ... не может, поэтому выдает ошибку.

SFINAE требует другого типа ошибок ... они возникают на первом этапе, когда проверяется, какие функции являются возможными совпадениями. Если простое создание функции приводит к ошибке, эта ошибка игнорируется, и функция не рассматривается как возможная перегрузка. После перечисления возможных перегрузок это подавление ошибок отключается, и вы застреваете с тем, что получаете.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...