Разрешение перегрузки конструктора 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 ++ довольно строг), но мне не хватает юриста по стандартам, чтобы иметь возможность распаковать то, что должно быть правильным поведением Вот!

1 ответ

Решение

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

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> является агрегатом, это нормально и успешно вызывает второй конструктор
  • двусмысленность здесь
  • Второй конструктор успешно вызван
Другие вопросы по тегам