C ++ Копировать Инициализация и прямая инициализация, странный случай

StackOverflow https://stackoverflow.com/questions/4801136

Вопрос

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

Сначала я суммирую правило здесь (прочтите стандарт N3225 8.5/16, 13.3.1.3, 13.3.1.4 и 13.3.1.5),

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

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

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

3.1) Во время этой пользовательской последовательности преобразования будут рассмотрены как преобразование CTOR (не эксплуатируемые CTOR), так и функции конверсии, не исключающие, в соответствии с правилами в 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 ++ имеет правило, которое возвращение функции принимается как инициализация копирования, согласно правилу выше, 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-Unqualified версию типа назначения. Временный - это 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!
}
Это было полезно?

Решение

Вы объявили свой конструктор копии 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, и он используется для прямого инициализации временного возврата (R).
Однако следует применять прямое инициализацию ко второму копированию от R до a?
Я не смог найти формулировку в текущем стандарте, которая явно указывает, что прямая инициализация должна применяться дважды.
Поскольку уверен, что эта последовательность портит explicit Спецификация в некотором смысле, я не удивлен, даже если разработчики компилятора думают, что это дефект.

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top