Оператор преобразования против удаленного конструктора

Пожалуйста, смотрите следующий код:

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поэтому компилятору разрешено использовать только конструкторы из-за прямой инициализации, поэтому вы получите ошибку.

Другие вопросы по тегам