C ++
Я читать что конструкторы без explicit
ключевое слово и с один параметр (или один-вызов аргумента ctor с несколькими параметрами, где все, кроме один имеют значения по умолчанию) может выполнять одно неявное преобразование. Учитывая два класса, где Foo
У ctor есть два int
параметры и Bar
У ctor есть два Foo
параметры, это утверждение, кажется, подразумевает призыв к Bar
Ctor с двумя неквалифицированными наборами int
ы не должны переводить в вызов Bar
Ctor с двумя 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
}
Утверждение не подразумевает ничего подобного. Это утверждение о конструкторах с одним параметром, и вы написали конструкторы с двумя параметрами. Это не относится.
Любая последовательность преобразования может содержать не более одного пользовательского преобразования (которое, в зависимости от контекста преобразования, может быть или не быть разрешено 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
Конструктор не может быть явным. Но это не считается частью цепочки преобразования инициализатора.
Статья, на которую вы ссылаетесь, написана на языке, предшествующем 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 запрещают использовать { ... }
инициализаторы с объектами, которые имеют определяемые пользователем конструкторы, за исключением ситуаций, когда внутри {}
,
Учитывая два класса, в которых 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
,
Эта строка немного сложнее:
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
,
Ясно, что Foo
может быть инициализирован из двух целых чисел:
Foo f = { 1, 2 };
Требуется одна конструкция, чтобы произвести первый аргумент Bar
типа Foo
из { 1, 2 }
то есть Foo{ 1, 2 }
, Требуется другая конструкция, чтобы произвести второй аргумент Bar
типа Foo
от { 3, 4 }
, На самом деле, не подразумевается никакого неявного преобразования.
Тем не менее, обратите внимание, что количество преобразований, необходимых для разных подвыражений, не учитывается при расчете одного преобразования: если у вас есть несколько подвыражений, каждое из которых включает одно преобразование для удовлетворения аргумента, это нормально.