Разрешение перегрузки конструктора C ++ 11 и initialiser_lists: clang ++ и g ++ не согласны

У меня есть небольшой фрагмент кода C ++ 11, который g ++ (4.7 или 4.8) отказывается компилировать, утверждая, что вызов конструктора для B2 b2a (x, {P (y)})) неоднозначен. Clang ++ доволен этим кодом, но отказывается компилировать B2 b2b (x, {{P (y)}}), который g ++ с удовольствием компилирует!

Оба компилятора полностью довольны конструктором B1, в качестве аргумента которого используется {…} или {{…}}. Может ли какой-нибудь адвокат языка C ++ объяснить, какой компилятор правильный (если есть) и что происходит? Код ниже:

#include <initializer_list>

using namespace std;

class Y {};
class X;

template<class T> class P {
public:
P(T);
};

template<class T> class A {
public:
A(initializer_list<T>);
};

class B1 {
public:
B1(const X&, const Y &);
B1(const X&, const A<Y> &);
};

class B2 {
public:
B2(const X &, const P<Y> &);
B2(const X &, const A<P<Y>> &);
};

int f(const X &x, const Y y) {
B1 b1a(x, {y});
B1 b1b(x, {{y}});
B2 b2a(x, {P<Y>(y)});
B2 b2b(x, {{P<Y>(y)}});
return 0;
}

и ошибки компилятора, лязг:

$ clang++ -stdlib=libc++ -std=c++11 test-initialiser-list-4.cc -o test.o -c
test-initialiser-list-4.cc:32:6: error: call to constructor of 'B2' is ambiguous
B2 b2(x, {{P<Y>(y)}});
^  ~~~~~~~~~~~~~~
test-initialiser-list-4.cc:26:5: note: candidate constructor
B2(const X &, const P<Y> &);
^
test-initialiser-list-4.cc:27:5: note: candidate constructor
B2(const X &, const A<P<Y>> &);
^

г ++:

test-initialiser-list-4.cc: In function 'int f(const X&, Y)':
test-initialiser-list-4.cc:32:21: error: call of overloaded 'B2(const X&, <brace-enclosed initializer list>)' is ambiguous
B2 b2(x, {P<Y>(y)});
^
test-initialiser-list-4.cc:32:21: note: candidates are:
test-initialiser-list-4.cc:27:5: note: B2::B2(const X&, const A<P<Y> >&)
B2(const X &, const A<P<Y>> &);
^
test-initialiser-list-4.cc:26:5: note: B2::B2(const X&, const P<Y>&)
B2(const X &, const P<Y> &);
^

Это пахнет как взаимодействие между равномерной инициализацией, синтаксисом списка инициализаторов и перегрузкой функций шаблонными аргументами (я знаю, что g ++ довольно строг), но мне не хватает юриста по стандартам, чтобы иметь возможность распаковать то, что должно быть правильным поведением Вот!

8

Решение

Сначала код, потом то, что я думаю должно произойти. (В дальнейшем я буду игнорировать первый параметр, поскольку нас интересует только второй параметр. Первый параметр всегда является точным соответствием в вашем примере). Обратите внимание, что правила в настоящее время постоянно меняются в спецификации, поэтому я бы не сказал, что у одного или другого компилятора есть ошибка.

B1 b1a(x, {y});

Этот код не может вызвать const Y& конструктор в C ++ 11, потому что Y это совокупность и Y не имеет члена данных типа Y (конечно) или что-то еще, инициализируемое им (это некрасиво и работает над исправлением — на компакт-диске C ++ 14 пока нет формулировки для этого, поэтому я не уверен, является ли окончательный вариант C ++ 14 будет содержать это исправление).

Конструктор с const A<Y>& параметр можно назвать — {y} будет принят в качестве аргумента для конструктора A<Y>и инициализирует этот конструктор std::initializer_list<Y>,

Отсюда — Второй конструктор успешно вызван.

B1 b1b(x, {{y}});

Здесь, в основном, тот же аргумент считает количество для конструктора с const Y& параметр.

Для конструктора с типом параметра const A<Y>&, это немного сложнее. Правило для стоимости преобразования в разрешении перегрузки, вычисляющее стоимость инициализации std::initializer_list<T> требует, чтобы каждый элемент ограниченного списка был преобразован в T, Однако мы раньше говорили, что {y} не может быть преобразован в Y (так как это совокупность). Теперь важно знать, std::initializer_list<T> является совокупным или нет. Честно говоря у меня есть без понятия должен ли он рассматриваться как совокупность в соответствии с положениями Стандартной библиотеки.

Если мы возьмем его неагрегированным, то мы будем рассматривать конструктор копирования std::initializer_list<Y>что, однако, опять-таки вызовет точно такую ​​же последовательность тестов (что приведет к «бесконечной рекурсии» при проверке разрешения перегрузки). Поскольку это довольно странно и нереализуемо, я не думаю, что какая-либо реализация пойдет по этому пути.

Если мы возьмем std::initializer_list чтобы быть агрегатом, мы будем говорить «нет, конверсия не найдена» (см. выше вопрос о агрегатах). В этом случае, поскольку мы не можем вызвать конструктор инициализатора с одним списком инициализаторов в целом, {{y}} будет разделен на несколько аргументов, и конструктор (ы) A<Y> будет принимать каждый из них в отдельности. Следовательно, в этом случае мы бы в конечном итоге {y} инициализация std::initializer_list<Y> в качестве единственного параметра — который прекрасно подходит и работает как шарм.

Так при условии, что std::initializer_list<T> является агрегатом, это нормально и успешно вызывает второй конструктор.

B2 b2a(x, {P<Y>(y)});

В этом и следующем случаях у нас нет совокупной проблемы, как указано выше, с Y больше, так как P<Y> имеет предоставленный пользователем конструктор.

Для P<Y> конструктор параметров, этот параметр будет инициализирован {P<Y> object}, Как P<Y> не имеет списков инициализаторов, список будет разбит на отдельные аргументы и вызовет Move-конструктор P<Y> с объектом Rvalue P<Y>,

Для A<P<Y>> параметр конструктора, он такой же, как и в предыдущем случае A<Y> инициализируется {y}: Поскольку std::initializer_list<P<Y>> может быть инициализирован {P<Y> object}, список аргументов не разделяется, и, следовательно, фигурные скобки используются для инициализации этого конструктора std::initializer_list<T>,

Теперь оба конструктора работают нормально. Они действуют как перегруженные функции, и их второй параметр в обоих случаях требует преобразования, определенного пользователем. Определенные пользователем последовательности преобразования можно сравнивать только в том случае, если в обоих случаях используется одна и та же функция преобразования или конструктор — здесь это не так. Следовательно, это неоднозначно в C ++ 11 (и в C ++ 14 CD).

Обратите внимание, что здесь у нас есть тонкий момент для изучения

struct X { operator int(); X(){/*nonaggregate*/} };

void f(X);
void f(int);

int main() {
X x;
f({x}); // ambiguity!
f(x); // OK, calls first f
}

Этот противоположный интуитивно понятный результат, вероятно, будет исправлен в том же прогоне с исправлением странности инициализации агрегата, упомянутой выше (оба вызовут первый f). Это реализуется, говоря, что {x}->X становится преобразованием личности (как есть X->x). В настоящее время это пользовательское преобразование.

Так, двусмысленность здесь.

B2 b2b(x, {{P<Y>(y)}});

Для конструктора с параметром const P<Y>&мы опять разделим аргументы и получим {P<Y> object} аргумент передан конструктору (-ам) P<Y>, Помни что P<Y> имеет конструктор копирования. Но сложность здесь в том, что нам не разрешено использовать его (см. 13.3.3.1p4), потому что это потребует преобразования, определенного пользователем. Остался только один конструктор Y, но Y не может быть инициализирован {P<Y> object},

Для конструктора с параметром A<P<Y>>, {{P<Y> object}} может инициализировать std::initializer_list<P<Y>>, так как {P<Y> object} конвертируется в P<Y> (кроме как с Y выше — черт, агрегаты).

Так, Второй конструктор успешно вызван.


Резюме для всех 4

  • Второй конструктор успешно вызван
  • при условии, что std::initializer_list<T> является агрегатом, это нормально и успешно вызывает второй конструктор
  • двусмысленность здесь
  • Второй конструктор успешно вызван
5

Другие решения

Других решений пока нет …