Обратите внимание на следующее:

struct A {
    A(int, int) { }
};

struct B {
    B(A ) { }                   // (1)
    explicit B(int, int ) { }   // (2)
};

int main() {
    B paren({1, 2});   // (3)
    B brace{1, 2};     // (4)
}

Конструкция brace в (4) явно и недвусмысленно вызывает (2). В clang конструкция paren в (3) однозначно вызывает (1), где, как и в gcc 5.2, она не может быть скомпилирована с:

main.cpp: In function 'int main()':
main.cpp:11:19: error: call of overloaded 'B(<brace-enclosed initializer list>)' is ambiguous
     B paren({1, 2});
                   ^
main.cpp:6:5: note: candidate: B::B(A)
     B(A ) { }  
     ^
main.cpp:5:8: note: candidate: constexpr B::B(const B&)
 struct B {
        ^
main.cpp:5:8: note: candidate: constexpr B::B(B&&)

Какой компилятор правильный? Я подозреваю, что clang здесь правильный, поскольку двусмысленность в gcc может возникнуть только через путь, который включает неявное построение B{1,2} и передачу его в конструктор копирования / перемещения - но этот конструктор помечен explicit, поэтому такой неявное построение недопустимо.

22
Barry 6 Янв 2016 в 01:06

2 ответа

Лучший ответ

Насколько я могу судить, это баг с лязгом .

Инициализация списка копирования имеет довольно неинтуитивное поведение: он считает явные конструкторы жизнеспособными до тех пор, пока разрешение перегрузки не будет полностью завершено, но затем может отклонить результат перегрузки, если выбран явный конструктор. Формулировка черновика после N4567, [over.match.list] p1

При инициализации списка копирования, если выбран конструктор explicit, инициализация неправильно сформирована. [ Примечание . Это отличается от других ситуации (13.3.1.3, 13.3.1.4), где только конвертирующие конструкторы рассматриваются для копирования-инициализации. Это ограничение применяется только если эта инициализация является частью окончательного результата перегрузки разрешающая способность. - конец примечания ]


Clang HEAD принимает следующую программу:

#include <iostream>
using namespace std;

struct String1 {
    explicit String1(const char*) { cout << "String1\n"; }
};
struct String2 {
    String2(const char*) { cout << "String2\n"; }
};

void f1(String1) { cout << "f1(String1)\n"; }
void f2(String2) { cout << "f2(String2)\n"; }
void f(String1) { cout << "f(String1)\n"; }
void f(String2) { cout << "f(String2)\n"; }

int main()
{
    //f1( {"asdf"} );
    f2( {"asdf"} );
    f( {"asdf"} );
}

И это, за исключением комментария к звонку f1, прямо из Бьярна Страуструпа. N2532 - Единая инициализация, глава 4. Спасибо Йоханнесу Шаубу за то, что он показал мне этот документ std-обсуждение.

В той же главе содержится следующее объяснение:

Настоящее преимущество explicit в том, что он отображает f1("asdf") ошибка. Проблема в том, что разрешение перегрузки «предпочитает» не - explicit конструкторы, так что f("asdf") вызывает f(String2). Я считаю разрешение f("asdf") ниже идеального, потому что автор String2, вероятно, не имел в виду устранить двусмысленность в пользу String2 (по крайней мере, не во всех случаях, когда явное и неявное конструкторы возникают следующим образом) и автор String1, безусловно, не сделал. Правило одобряет «неряшливых программистов», которые не используют explicit.


Насколько мне известно, N2640 - Списки инициализаторов - Альтернативный механизм и обоснование - это последний документ, который включает обоснование такого рода. разрешения перегрузки; его преемник N2672 был включен в проект C ++ 11.

Из главы «Значение явного»:

Первый подход к тому, чтобы сделать пример плохо сформированным, - это потребовать, чтобы все конструкторы (явные и неявные) рассматривались для неявных преобразований, но если явный конструктор оказывается выбранным, эта программа плохо сформирована. Это правило может преподнести свои сюрпризы; например:

struct Matrix {
    explicit Matrix(int n, int n);
};
Matrix transpose(Matrix);

struct Pixel {
    Pixel(int row, int col);
};
Pixel transpose(Pixel);

Pixel p = transpose({x, y}); // Error.

Второй подход - игнорировать явные конструкторы при поиске для жизнеспособности неявного преобразования, но включать их, когда Фактический выбор конструктора преобразования: если явный конструктор оказывается выбранным, программа имеет неправильный формат. Этот альтернативный подход позволяет работать последнему примеру (Pixel-vs-Matrix) как и ожидалось (выбрано transpose(Pixel)), при этом исходный пример ("X x4 = { 10 };") плохо сформирован.

Хотя в этой статье предлагается использовать второй подход, его формулировка кажется ошибочной - в моей интерпретации формулировки она не приводит к поведению, описанному в обосновательной части статьи. Формулировка изменена в N2672 для использования первого подхода, но я не смог найти обсуждения, почему это было изменено.


Конечно, в инициализации переменной используется немного больше формулировок, чем в OP, но, учитывая разницу в поведении между clang и gcc, одинаковую для первого примера программы в моем ответе, я думаю, что это охватывает основные моменты.

9
Community 23 Май 2017 в 12:16

Это не полный ответ, хотя он и слишком длинный для комментария.
Я попытаюсь предложить контрпример к вашим рассуждениям, и я готов к голосованию против, поскольку я далек от уверенности.
В любом случае, давайте попробуем !! :-)

Это следует сокращенному примеру:

struct A {
    A(int, int) { }
};

struct B {
    B(A) { }
    explicit B(int, int ) { }
};

int main() {
    B paren({1, 2});
}

В этом случае оператор {1, 2}, по-видимому, уступает место двум решениям:

  • прямая инициализация с помощью B(A), потому что A(int, int) не является явным и, следовательно, разрешено, и это фактически первый кандидат

  • по той же причине, что и выше, его можно интерпретировать как B{B(A{1,2})} (ну, позвольте мне злоупотребить обозначениями, чтобы дать вам представление о том, что я имею в виду), то есть {1,2} позволяет построить {{X2 }} временный объект, который используется сразу после в качестве аргумента для конструктора копирования / перемещения, и он снова разрешен, поскольку задействованные конструкторы не являются явными

Последний объяснил бы второго и третьего кандидатов.

Есть ли в этом смысл?
Я готов удалить ответы, если вы объясните мне, что не так в моих рассуждениях. :-)

0
skypjack 6 Янв 2016 в 00:10