Конструкторы и преобразования

C ++

Я читать что конструкторы без explicit ключевое слово и с один параметр (или один-вызов аргумента ctor с несколькими параметрами, где все, кроме один имеют значения по умолчанию) может выполнять одно неявное преобразование. Учитывая два класса, где FooУ ctor есть два int параметры и BarУ ctor есть два Foo параметры, это утверждение, кажется, подразумевает призыв к BarCtor с двумя неквалифицированными наборами intы не должны переводить в вызов BarCtor с двумя Foo«S. Тогда почему следующий компилируется?

struct Foo {
Foo(int x_, int y_) : x(x_), y(y_) {}
int x;
int y;
};

struct Bar {
Bar(Foo first_, Foo second_) : first(first_), second(second_) {}
Foo first;
Foo second;
};

#include <iostream>

int main() {
Bar b = { { 1, 2 }, { 3, 4 } };
std::cout << b.first.x << ", " << b.first.y << ", ";
std::cout << b.second.x << ", " << b.second.y << std::endl;
// ^ Output: 1, 2, 3, 4
}

2

Решение

Утверждение не подразумевает ничего подобного. Это утверждение о конструкторах с одним параметром, и вы написали конструкторы с двумя параметрами. Это не относится.

Любая последовательность преобразования может содержать не более одного пользовательского преобразования (которое, в зависимости от контекста преобразования, может быть или не быть разрешено explicit — в случае, если вы опишите это, возможно, нет). Здесь есть две отдельные последовательности преобразования: одна из списка инициализаторов для первого аргумента Barконструктор и другой, чтобы получить из другого списка инициализатора второй аргумент. Каждая последовательность содержит одно преобразование в Foo, Вот почему ваш код в порядке.

То, что вы не можете сделать по правилу «одно пользовательское преобразование», это:

struct Foo { Foo(int) {} };
struct Bar { Bar(const Foo&) {} };
struct Baz { Baz(const Bar&) {} };

Baz baz(1);

потому что это потребует двух пользовательских преобразований в цепочке. Но Baz baz(Foo(1)); Это нормально, поскольку для получения аргумента, который вы указали, требуется только одно преобразование (экземпляр Foo) к аргументу Baz может принять (экземпляр Bar). Baz baz(Bar(1L)) тоже хорошо, потому что хотя есть два преобразования long -> int -> Foo чтобы получить от 1L на аргумент, который Bar() Можно принять, одно из преобразований является встроенным, а не определенным пользователем.

Вы также не можете сделать это:

Bar bar = 1;

потому что это инициализация копирования, и он сначала пытается преобразовать правую часть в Bar, В цепочке нужны два пользовательских преобразования int -> Foo -> Barтак что это не хорошо. Но вы можете сделать это:

Bar bar = { 1 };

Это эквивалентно в этом случае Bar bar(1);не Bar bar = 1;, Есть разница в целом между T t = { 1 }; а также T t(1)что это с T t = { 1 }; T Конструктор не может быть явным. Но это не считается частью цепочки преобразования инициализатора.

2

Другие решения

Статья, на которую вы ссылаетесь, написана на языке, предшествующем C ++ 11, в котором полностью игнорируются новые возможности C ++ 11, связанные с равномерной инициализацией. В более старых версиях языка пользовательские преобразования могли использоваться неявно для преобразования из не замужем только объект

В C ++ 11 стало возможным использовать многопараметрические конструкторы для создания (и неявного преобразования) объектов из инициализатора { ... } форма, где каждый последующий элемент между {} передается в качестве соответствующего аргумента конструктора. Эта форма неявного преобразования также может быть отключена explicit ключевое слово.

Это преобразование C ++ 11 именно то, что происходит в вашем примере кода. Во-первых, { 1, 2 } инициализатор преобразуется в Foo введите с помощью этой новой функции C ++ 11. Тогда { Foo(1,2), Foo(3, 4) } преобразуется в Bar введите через ту же функцию C ++ 11.

Размещенный вами код не будет принят компилятором до C ++ 11. Фактически, компиляторы до C ++ 11 запрещают использовать { ... } инициализаторы с объектами, которые имеют определяемые пользователем конструкторы, за исключением ситуаций, когда внутри {},

2

Учитывая два класса, в которых ctor Foo имеет два параметра int, а ctor Бар имеет два параметра Foo, это утверждение, по-видимому, подразумевает вызов ctor Бара с двумя неквалифицированными наборами целых чисел, который не должен переводиться в вызов ctor Бара с двумя Foo. Тогда почему следующий компилируется?

Я не уверен, как вы поняли это значение, но это неправильный путь: если Fooдва int конструктора были объявлены explicit, затем код не будет компилироваться, потому что преобразование из списка инициализатора в Foo не сработает

 Foo f = {1,2}; // error if Foo(int, int) is explicit

Без explicitэто преобразование разрешено, и в построении bот списка инициализаторов до Foo,

1

Эта строка немного сложнее:

Bar b = { { 1, 2 }, { 3, 4 } };

Это утверждение, которое будет пытаться построить Bar, Возьмите конструктор Bar:

Bar(Foo first_, Foo second_);

Хорошо, так что нужно построить 2 Fooнет проблем, посмотрим, что за конструктор Fooявляется :

Foo(int x_, int y_);

Чертовски верно! Мы только что получили два { int, int } ! Как заметил @juanchopanza, именно здесь происходит конверсия. Я не рассматривал список инициализированных скобок для (int, int) как преобразование, но, как вы можете видеть, если вы положили explicit на вашем конструкторе это одно:

converting to ‘Foo’ from initializer list would use explicit constructor ‘Foo::Foo(int, int)’
Bar b = { { 1, 2 }, { 3, 4 } };

Кстати, как указано в комментарии, если вы явно использовали std::initializer_list он не компилируется: http://ideone.com/5enNFU

Так что это построить два Foo с этими 2 инициализаторами переоборудованный используя ваш конструктор, он передает его Bar конструктор, преобразующий список инициализаторов, содержащий два Foo :

Bar(Foo first_, Foo second_) : first(first_), second(second_) {}

И этот конструктор просто вызвать 2 копировать конструктор Foo так как first а также second имеют тип Foo,

1

Ясно, что Foo может быть инициализирован из двух целых чисел:

Foo f = { 1, 2 };

Требуется одна конструкция, чтобы произвести первый аргумент Bar типа Foo из { 1, 2 }то есть Foo{ 1, 2 }, Требуется другая конструкция, чтобы произвести второй аргумент Bar типа Foo от { 3, 4 }, На самом деле, не подразумевается никакого неявного преобразования.

Тем не менее, обратите внимание, что количество преобразований, необходимых для разных подвыражений, не учитывается при расчете одного преобразования: если у вас есть несколько подвыражений, каждое из которых включает одно преобразование для удовлетворения аргумента, это нормально.

1
По вопросам рекламы [email protected]