Оператор преобразования против удаленного конструктора
Пожалуйста, смотрите следующий код:
struct X;
struct Y {
Y() {}
Y(X&) = delete;
};
struct X {
X() {}
operator Y() {
return{};
}
};
int main() {
X x;
static_cast<Y>(x);
}
Здесь Y
конструктор брал X
явно удален, в то время как X
имеет оператор преобразования в Y
, Кажется, что среди этих двух противоречащих друг другу =delete
всегда выигрывать; Я тестировал на некоторых последних версиях GCC, Clang и VC++.
Вопрос: это "правильное" поведение? Я думал, что между конструктором преобразования и оператором преобразования нет особого приоритета, поэтому приведенный выше код должен привести к ошибке неоднозначности разрешения перегрузки. Но это не так. Он жалуется на использование удаленной функции. Это из-за гарантированной копии?
Я гуглил и нашел конструктор преобразования против оператора преобразования: приоритет. В этом вопросе, оператор преобразования был выбран, потому что это было лучшее соответствие из-за присутствия const
в конструкторе преобразования. Однако в моем случае замена Y(X&)
в Y(X const&)
ничего не изменилось.
На самом деле, я хочу иметь следующую ситуацию:
X x;
Y y1(x); // Error
Y y2 = static_cast<Y>(x); // OK
Да, это можно назвать глупым, но на самом деле есть встроенные типы, которые ведут себя так: X <- int&
, Y <- int&&
, Невозможность создать пользовательский тип, который точно имитирует встроенный ссылочный тип, кажется действительно отчаянно отсутствующей частью в текущем C++...
2 ответа
Вопрос: это "правильное" поведение? Я думал, что нет особого приоритета между конструктором преобразования и оператором преобразования [...]
Это не совсем верно. Вы смотрите на код как есть:
struct Y {
Y() {}
Y(X&) = delete;
};
Но на самом деле там есть нечто большее. Для компилятора, Y
похоже:
struct Y {
Y() {}
Y(X&) = delete;
Y(Y&&) = default;
Y(Y const&) = default;
};
Выбор здесь не между Y(X&)
а также X::operator Y()
, Выбор в основном между Y(X&)
а также Y(Y&&)
, И первое лучше, чем второе (независимо от того, как вы упоминаете в вопросе, это X&
или же X const&
в качестве параметра). Но он удален, поэтому преобразование является неправильным.
Если бы мы инициализировали копию вместо прямой инициализации:
Y y = x;
Тогда оба были бы одинаково жизнеспособными (и, следовательно, неоднозначными). И да, вы действительно хотите, чтобы это было неоднозначным. = delete
не удаляет из набора кандидатов!
Изменение конструктора из Y(X&)
в Y(X const&)
будет иметь функцию преобразования предпочтительнее.
Да, это можно назвать глупым, но на самом деле есть встроенные типы, которые ведут себя так:
X <- int&
,Y <- int&&
Да, но в оригинальном примере X
а также Y
разные типы. Здесь они представляют разные категории значений одного и того же типа. Причина, по которой этот новый пример работает или не работает, совершенно иная:
X x;
Y y1(x); // Error
Y y2 = static_cast<Y>(x); // OK
действительно:
int& x = ...;
int&& y(x); // error, can't bind rvalue reference to lvalue
int&& y2 = static_cast<int&&>(x); // ok. this is exactly std::move(x)
Привязка ссылок к ссылочно-совместимым типам - не тот же вопрос, что и приоритет преобразования.
От стандарта 11.6.17.6.2
если инициализация является прямой инициализацией, или если это инициализация копированием, когда cv-неквалифицированная версия исходного типа является тем же классом, или производным классом класса назначения, рассматриваются конструкторы.
Тогда стандарт говорит нам, что (11.6.16
)
Инициализация, которая происходит в формах [...], а также в
new
выражения (8.5.2.4),static_cast
выражения (8.5.1.9), преобразования типов функциональной нотации (8.5.1.3), mem-initializer (15.6.2) и форма условия braced-init-list условия называются прямой инициализацией.
Ваш пример инициализирует временное static_cast
поэтому компилятору разрешено использовать только конструкторы из-за прямой инициализации, поэтому вы получите ошибку.