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

Прежде чем продолжить чтение, прочитайте есть ли в 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). Затем преобразованное временное значение 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 целевого типа. Временное является ценностью. Результат вызова (который является временным для случая конструктора) затем используется для прямой инициализации в соответствии с приведенными выше правилами объекта, который является местом назначения инициализации копирования. В некоторых случаях реализации разрешается устранять копирование, присущее этой прямой инициализации, путем создания промежуточного результата непосредственно в инициализируемом объекте; см. 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!
}

person user534498    schedule 26.01.2011    source источник
comment
Comeau Online принимает весь фрагмент кода в том виде, в котором он написан.   -  person James McNellis    schedule 26.01.2011
comment
хорошо спасибо. Это дает мне некоторую уверенность в том, что мои рассуждения, по крайней мере, не идут в неправильном направлении. ^_^   -  person user534498    schedule 26.01.2011
comment
@James McNellis: Тем не менее, Comeau Online отклоняет код в моем ответе, хотя, согласно рассуждениям OP, он должен быть принят.   -  person AnT    schedule 26.01.2011
comment
Привет Андрей, согласно стандарту, код в вашем примере должен быть отклонен, он находится в категории Для инициализации копирования, а тип источника совпадает с типом назначения или получен из типа назначения, где не применяется определенное пользователем преобразование, в этом В этом случае учитываются только преобразователи (имеются в виду явные кторы). Согласно стандарту, только когда применяется определяемое пользователем преобразование, результат преобразования используется для ПРЯМОЙ инициализации (которая при необходимости использует явные конструкторы).   -  person user534498    schedule 26.01.2011


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

person AnT    schedule 26.01.2011
comment
привет, ты не читал правила стандарта 8.5/16. Во время инициализации копирования прямая инициализация будет использоваться после определяемого пользователем преобразования, которому, конечно же, будет разрешено использовать явный конструктор копирования. - person user534498; 26.01.2011
comment
@user534498: user534498: Что ж, 12.3.1/2, похоже, требует прямой инициализации синтаксиса, чтобы явные конструкторы стали доступными. Простой прямой инициализации недостаточно, синтаксис должен присутствовать в коде. А в вашем его нет. - person AnT; 26.01.2011
comment
Привет, Андрей! Пожалуйста, прочтите мое редактирование 1. Кстати, я думаю, что пример в 12.3.1/2 не противоречит моим рассуждениям выше, потому что он не включает явный конструктор копирования. - person user534498; 26.01.2011
comment
@ user534498: Я не говорю, что пример в 12.3.1/2 чему-то противоречит. Я просто говорю, что слово синтаксис, присутствующее в формулировке 12.3.1/2, имеет (или может иметь) важное значение в данном случае. Этот момент также упоминается в DR # 152, хотя кажется, что предлагаемое разрешение должно включить ваш код. - person AnT; 26.01.2011
comment
Мое прочтение предлагаемого решения заключается в том, что оно разъясняет, что код OP запрещен: нет свободного от синтаксиса вызова явных конструкторов. - person Martin Dorey; 24.01.2013

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

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

person Ise Wisteria    schedule 26.01.2011