c++ инициализация копирования и прямая инициализация, странный случай

прежде чем продолжить чтение, пожалуйста, прочитайте есть ли разница в C++ между инициализацией копирования и прямой инициализацией? во-первых, убедитесь, что вы понимаете, о чем идет разговор.

сначала я подытожу правило здесь (прочитайте стандарт n3225 8.5 / 16, 13.3.1.3, 13.3.1.4 и 13.3.1.5),

1) Для сразу инициализации, все конструкторы будут рассмотрены как перегружая набор, перегружая разрешение выберет самое лучшее одно согласовывая к правилам разрешения перегрузки.

2) для инициализации копирования и тип источника совпадает с типом назначения или производным от типа назначения, правило такое же, как и выше, за исключением того, что только преобразование конструкторов (конструкторов без явного) будет считаться набором перегрузки. Это фактически означает, что явные конструкторы копирования/перемещения не будут рассматриваться в наборе перегрузки.

3) для случаев инициализации копирования, не включенных в (2) выше (источник type отличается от destination type и не является производным от destination type), сначала мы рассмотрим пользовательские последовательности преобразования, которые могут преобразовываться из исходного типа в целевой тип или (когда используется функция преобразования) в его производный класс. Если преобразование выполняется успешно, результат используется для прямой инициализации объект назначения.

3.1) во время этой пользовательской последовательности преобразования как преобразование ctors (неявные ctors), так и в соответствии с правилами, содержащимися в пунктах 8.5/16 и 13.3.1.4, будут рассмотрены неявные функции преобразования.

3.2) результат prvalue будет прямой инициализации объект назначения, как правила, перечисленные в (1), см 8.5/16.

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

struct A
{
    A (int) { }
    A() { }
    explicit A(const A&) { }
};
struct B
{
    operator A() { return 2; }
    //1) visual c++ and clang passes this
    //gcc 4.4.3 denies this, says no viable constructor available
};
int main()
{
    B b;
    A a = b;
    //2) oops, all compilers deny this
}

в моем понимании, для (1),

operator A() { return 2; }

поскольку C++ имеет правило, что функция return берется как инициализация копирования, согласно правилу выше, 2 сначала будет неявно преобразован в A, что должно быть в порядке, потому что A имеет конструктор A(int). Затем преобразованный временный prvalue будет использоваться для прямой инициализации возвращаемого объекта, который также должен быть в порядке, потому что прямая инициализация может использовать явный конструктор копирования. Так что GCC ошибается.

для (2),

A a = b;

в моем понимании, сначала b неявно преобразуется в A, оператором A (), а затем преобразованное значение должно использоваться для прямой инициализации a, что, конечно, может вызвать явный конструктор копирования? Таким образом, это должно пройти компиляцию и все компиляторы неправильно?

обратите внимание, что для (2) visual C++ и clang имеют ошибку, аналогичную, "Ошибка, не удается преобразовать из B В A", но если я удалю явное ключевое слово в конструкторе копирования A, ошибка уже нет..

Спасибо за чтение.


изменить 1

потому что кто-то все еще не понял, что я имел в виду, я цитирую следующий стандарт из 8.5/16,

в противном случае (т. е. для остальных случаи инициализации копирования), определенное пользователем преобразование последовательности можно преобразовать из исходного типа в тип назначения или (когда функция преобразования используется) в перечисляются производные классы как описано в 13.3.1.4, и лучшие одно выбрано через перегрузку разрешение (13.3). Если преобразование не может быть сделано или неоднозначно, инициализация-это плохо сформированные. Этот выбранная функция вызывается с помощью выражение инициализатора как его аргумент; если функция является конструктор, вызов инициализирует временный cv-неквалифицированный версия типа назначения. Этот временное-это prvalue. Результат вызов (который является временным для случай конструктора) тогда привыкший прямой инициализации, согласно правила выше, объект, который является назначения копировать-инициализация. В некоторых случаях, реализация разрешается исключите копирование, присущее этому прямая инициализация путем построения промежуточный результат сразу в инициализируемый объект; см. 12.2, 12.8.

обратите внимание, что он упомянул прямую инициализацию после пользовательского преобразования. Что означает, в моем понимании, следующий код должен подчиняться правилам, как я прокомментировал, что подтверждается как clang, coomeau online, visual C++, но GCC 4.4.3 терпит неудачу как (1), так и (2). Хотя это странное правило, но оно следует рассуждениям из стандарта.

struct A
{
    A (int) { }
    A() { }
    explicit A(const A&) { }
};

int main()
{
    A a = 2;    //1)OK, first convert, then direct-initialize
    A a = (A)2; //2)oops, constructor explicit, not viable here!
}

2 ответов


вы объявили свой конструктор копирования explicit (кстати, почему?), что означает, что он больше не может использоваться для неявного копирования объектов класса. Чтобы использовать этот конструктор для копирования, теперь вы вынуждены использовать синтаксис прямой инициализации. См. 12.3.1/2

2 явный конструктор создает объекты так же, как неявные конструкторы, но делает это только там, где синтаксис прямой инициализации (8.5) или где приведения (5.2.9, 5.4) явно используемый.

проблема может быть проиллюстрирована следующим гораздо более коротким примером

struct A {
  A() {}
  explicit A(const A&) {}
};

int main() {
  A a;
  A b = a; // ERROR: copy-initialization
  A c(a); // OK: direct-initialization
}

это то, что блокирует все ваши преобразования от работы, так как все они полагаются на инициализацию копирования, которая, в свою очередь, полагается на неявное копирование. И вы отключили неявное копирование.

дополнительно см. Отчет О Дефекте #152 который охватывает этот конкретный вопрос. Хотя я не уверен, каковы последствия "предлагаемой резолюции" должен быть...


как вы упомянули, gcc не компилирует следующий код.

struct A {
  A( int ) {}
  explicit A( A const& ) {}
};

int main() {
  A a = 2;
}

Я думаю, что это не соответствует стандарту в соответствии с текущим стандартом 8.5 p15.
Однако, что касается вашего первого случая,

struct A {
  A( int ) {}
  explicit A( A const& ) {}
};

struct B {
  operator A() { return 2; }
};

int main() {
  B b;
  A a = b;
}

Я не уверен,что это соответствует.
Как вы знаете, return вызовет копирование дважды концептуально.
return 2; создает A неявно от int 2, и он использован к direct-инициализация временного возвращаемое значение (R).
Однако следует ли применять прямую инициализацию ко второму копированию от R к a?
Я не мог найти формулировку в текущем стандарте, которая явно гласит, что direct-инициализация должна применяться дважды.
Так как несомненно, что эта последовательность портится explicit спецификация в a смысл, я не удивлен, даже если разработчики компилятора думали, что это позволяет это дефект.

позвольте мне сделать лишнее дополнение.
Компилятор несоответствующее поведение не означает, что компилятор имеет дефект напрямую.
Стандарт уже имеет дефекты, как показывают отчеты о дефектах.
Например, стандарт C не разрешает преобразование из указателя в массив типа T, к указателю на массив const т.
Это разрешено в C++, и я думаю, что это должно быть разрешено семантически в C аналогично.
Gcc выдает предупреждение об этом преобразовании. Comeau выдает ошибку и не компилируется.