инициализация копирования c ++ и прямая инициализация, странный случай - PullRequest
6 голосов
/ 26 января 2011

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

Сначала я обобщу правило (см. Стандарт n3225 8.5 / 16, 13.3.1.3, 13.3.1.4 и 13.3.1.5),

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

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

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

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 ++ есть правило, что возвращение функции принимается как инициализация копирования, согласно правилу выше, 2 будет сначала неявно преобразовано в A, что должно быть в порядке, потому что A имеет конструктор A (int). Затем преобразованное временное значение будет использоваться для прямой инициализации возвращаемого объекта, что тоже должно быть в порядке, поскольку прямая инициализация может использовать явный конструктор копирования. Так что 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). Если преобразование не может быть сделано или является неоднозначным, инициализация плохо сформирована. выбранная функция вызывается с выражение инициализатора как его аргумент; если функция конструктор, вызов инициализирует временный из резюме версия типа назначения. временное является 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 ]

8 голосов
/ 26 января 2011

Вы объявили свой конструктор копирования 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 , в котором описана эта конкретная проблема. Хотя я не уверен, какими должны быть последствия "предложенной резолюции" ...

0 голосов
/ 26 января 2011

Как вы упомянули, 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?
IНе удалось найти формулировку в текущем стандарте, в которой прямо указано, что прямую инициализацию следует применять дважды.
Так как определенно, что эта последовательность в некотором смысле портит спецификацию explicit, я не удивлюсь, даже если разработчики компилятора подумалидопущение этого является дефектом.

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

...