У меня есть небольшой фрагмент кода 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 ++ довольно строг), но мне не хватает юриста по стандартам, чтобы иметь возможность распаковать то, что должно быть правильным поведением Вот!
Сначала код, потом то, что я думаю должно произойти. (В дальнейшем я буду игнорировать первый параметр, поскольку нас интересует только второй параметр. Первый параметр всегда является точным соответствием в вашем примере). Обратите внимание, что правила в настоящее время постоянно меняются в спецификации, поэтому я бы не сказал, что у одного или другого компилятора есть ошибка.
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>
является агрегатом, это нормально и успешно вызывает второй конструкторДругих решений пока нет …