Почему конвертируемость типов в C ++ не транзитивна? - PullRequest
2 голосов
/ 15 апреля 2019

Рассмотрим следующие статические утверждения:

    static_assert(std::is_convertible_v<int const&, int const>);
    static_assert(std::is_convertible_v<int const, int>);
    static_assert(std::is_convertible_v<int, int &&>);

    static_assert(std::is_convertible_v<int const&, int &&>);

Вышеупомянутые три утверждения проходят, но последнее утверждение не выполняется.

Это означает, что конвертируемость типов в C ++ в целом не транзитивна, чтоЯ думаю, что это очень нелогично.

Я искал стандарт и веб-сайт cppreference, чтобы найти какие-либо доказательства того, что это предполагаемое поведение, но я пока не добился успеха.

Интересно, для lvalue-ссылки, все хорошо, потому что std::is_convertible_v<int, int&> ложно.Я также ожидал бы, что для rvalue-ссылок.

Я предполагаю, что это как-то связано с тем, как определяется is_convertible.В определении аргумент To появляется как тип возврата мнимой функции.Насколько я понимаю, свежее значение любого типа является временным и поэтому может быть преобразовано в rvalue-ссылку.Поэтому std::is_convertible_v<T, T&&> справедливо для любого типа T.

Поэтому, в частности, я задаю следующие вопросы:

  1. Действительно ли is_convertible захватывает интуицию конвертируемости?
  2. Если нет, что еще он захватывает?Или по-другому: не подходит ли моя интуиция конвертируемости?
  3. Если мы понимаем is_convertible как бинарное отношение, не должно ли это быть предзаказом, то есть транзитивным?Почему бы и нет?

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

В частности, T не должен быть конвертируемым в T&&, потому что вы не можете использовать T, где требуется T&& (вы не можете перейти от Tнапример, но вы можете перейти от T&&).

Я что-то здесь серьезно ошибаюсь?

Ответы [ 2 ]

3 голосов
/ 15 апреля 2019

Интуитивно, imho, конвертируемость должна означать: всякий раз, когда требуется тип To, вы также можете использовать тип From ....

И это то, что он означает.

... И это будет означать транзитивность.

Нет, это не правильно.Не каждое бинарное отношение должно быть транзитивным.С cppreferene о неявных преобразованиях:

Последовательность неявного преобразования состоит из следующего в следующем порядке:

1) ноль или одна стандартная последовательность преобразования;

2) ноль или одно пользовательское преобразование;

3) ноль или одна стандартная последовательность преобразования.

При рассмотрении аргумента для конструктора или пользовательской функции преобразования допускается только одна стандартная последовательность преобразования (в противном случае пользовательские преобразованияможет быть эффективно прикован).При преобразовании из одного встроенного типа в другой встроенный тип допускается только одна стандартная последовательность преобразования.

Точные правила довольно сложны, но учитывают «ноль или одно пользовательское преобразование;"поэтому, если у вас есть определенные пользователем преобразования из Foo в Bar и из Bar в Baz, это не обязательно означает, что Foo преобразуется в Baz!

.не std::is_convertible, который имеет странное понятие конвертируемости, но правила в C ++ относительно того, что конвертируемо, не являются переходными для начала.

1 голос
/ 15 апреля 2019

Это означает, что конвертируемость типов в C ++ в целом не транзитивна, что я считаю очень противоречивым.

Вообще говоря, вы не хотите конвертировать const T& в T&&. Это может иметь катастрофические последствия. Вы бы хотели ошибку компилятора, чтобы случайно не std::move данные вызывающего абонента из них (или, альтернативно, создать непреднамеренную копию, которая выглядит , как будто копия не была сделана)


Теперь, что стандарт должен сказать по этому поводу? [ко]

A стандартная последовательность преобразований - это последовательность стандартных преобразований в следующем порядке:
- Ноль или одно преобразование из следующего набора: преобразование lvalue-в-значение, преобразование массива в указатель и преобразование функции в указатель.
- Ноль или одно преобразование из следующего набора: интегральные преобразования, повышение с плавающей запятой, интегральные преобразования, преобразования с плавающей запятой, преобразования с плавающей запятой, преобразования указателей, преобразования указателей в члены и логические преобразования.
- Нулевое или одно преобразование указателя функции.
- Ноль или одна квалификация конверсии.

Таким образом, мы можем неявно преобразовать int const& в int через преобразование lvalue-в-значение (для типов, не относящихся к классам, оно удаляет квалификацию cv).

И мы также можем неявно преобразовывать int в int&& через диалог идентификации (преобразование не требуется, поскольку мы можем выполнить привязку ссылки prvalue к ссылке rvalue).

Но мы не можем неявно преобразовать int const& в int&&, потому что для этого потребуется

  • преобразование lvalue в rvalue (преобразование Lvalue), int const& в int, за которым следует
  • Преобразование идентичности (привязка ссылки), int в int const&

Это потому, что мы не можем смешивать преобразование идентичности с другими преобразованиями таким образом, согласно [over.ics.scs] :

стандартная последовательность преобразования либо является преобразованием идентичности сама по себе (то есть без преобразования), либо состоит из одного-трех преобразований из других четырех категорий.

(Категории определяются таблицей в [over.ics.scs] )

...