Почему выбрана эта перегрузка оператора преобразования?

27

Рассмотрим следующий код .

struct any
{
    template <typename T>
    operator T &&() const;

    template <typename T>
    operator T &() const;
};
int main()
{
    int a = any{};
}

Здесь второй оператор преобразования выбирается разрешением перегрузки. Почему?

Насколько я понимаю, два оператора выводятся operator int &&() constи operator int &() constсоответственно. Оба находятся в наборе жизнеспособных функций. Прочтение [over.match.best] не помогло мне понять, почему последнее лучше.

Почему последняя функция лучше первой?

avakar
источник
Я простой кот, и я бы сказал, что это должно быть второй раз, когда введение другого было бы серьезным изменением в C ++ 11. Это будет где-то в стандарте. Хороший вопрос, хотя, есть upvote. (Внимание экспертов: если этот комментарий фигня, то, пожалуйста, скажите мне!)
Вирсавия
3
FWIW, template <typename T> operator T &&() const &&; template <typename T> operator T &() const &;заставляет это звонить первому.
Натан Оливер
5
@LightnessRaceswithMonica Операторы преобразования позволяют это сделать, иначе было бы невозможно иметь несколько операторов преобразования.
Натан Оливер
1
@Bathsheba Я не думаю, что причина этого. Это все равно, что сказать, что конструкторы перемещения никогда не могут быть выбраны по разрешению перегрузки, потому что это будет серьезное изменение. Если вы пишете конструктор перемещения, вы выбираете, чтобы ваш код был «нарушен» по этому определению.
Брайан
1
@LightnessRaceswithMonica То, как я держу это в голове, я воспринимаю тип возвращаемого значения как имя функции. Разные типы равны разным именам равны успеху :)
NathanOliver

Ответы:

13

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

См. C ++ 17 [temp.deduct.partial] / (3.2):

В контексте вызова функции преобразования используются типы возврата шаблонов функции преобразования.

и / 9:

Если для данного типа, дедукция преуспевает в обоих направлениях (т.е. типы идентичны после вышеуказанных преобразований) и как Pи Aбыли ссылочные типы (до заменяются типа упомянутых выше): - если тип от аргумента шаблона была ссылка lvalue, а тип из шаблона параметра не был, тип параметра не считается, по крайней мере, таким же специализированным, как тип аргумента; ...

Брайан
источник
12

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

И при выборе между T&&и T&на T&победы в правилах разрешения перегрузки. Это разрешить:

template<class T>
void f( T&& ) { std::cout << "rvalue"; }
template<class T>
void f( T& ) { std::cout << "lvalue"; }

работать. T&&может соответствовать lvalue, но когда доступны как lvalue, так и универсальные эталонные перегрузки, lvalue предпочтительнее.

Правильный набор операторов преобразования, вероятно:

template <typename T>
operator T&&() &&;

template <typename T>
operator T &() const; // maybe &

или даже

template <typename T>
operator T() &&;

template <typename T>
operator T &() const; // maybe &

чтобы предотвратить неудачное продление жизни от укуса.

3 Типы, используемые для определения порядка, зависят от контекста, в котором выполняется частичное упорядочение:

[СНиП]

(3.2) В контексте вызова функции преобразования используются типы возврата шаблонов функций преобразования.

Что в конечном итоге зависит от «более специализированных» правил при выборе перегрузок:

(9.1) если тип из шаблона аргумента был ссылкой lvalue, а тип из шаблона параметра не был, тип параметра не считается, по крайней мере, таким же специализированным, как тип аргумента; в противном случае,

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

Более специализированные шаблоны выигрывают при разрешении перегрузки меньше, при прочих равных условиях.

Якк - Адам Невраумонт
источник
Кто-то добавил тег « язык-юрист», когда я отвечал; Я мог бы просто удалить. У меня есть смутное воспоминание о чтении текста, в котором говорится, что он рассматривается как параметр для выбора того, какой из них вызывается, и текста, который говорит о том, как обрабатывается &&vs &при выборе перегрузок шаблона, но дразнить точный текст может занять некоторое время. ,
Якк - Адам Невраумонт
4

Мы пытаемся инициализировать intиз any. Процесс для этого это:

  1. Выясните, как мы можем это сделать. То есть определяют всех наших кандидатов. Они происходят из неявных функций преобразования, в которые можно преобразовать intчерез стандартную последовательность преобразования ( [over.match.conv] ). Раздел содержит эту фразу:

    Вызов функции преобразования, возвращающей «ссылку на X», является типом glvalue типа X, и поэтому считается, что такая функция преобразования приводит Xк этому процессу выбора функций-кандидатов.

  2. Выберите лучшего кандидата.

После шага 1 у нас есть два кандидата. operator int&() constи operator int&&() constоба из них считаются полезными intдля целей выбора функций-кандидатов. Какой лучший кандидат уступает int?

У нас есть тай- брейкер, который предпочитает ссылки lvalue ссылкам rvalue ( [over.ics.rank] /3.2.3 ). Однако здесь мы не привязываем ссылку, и пример там несколько перевернут - это для случая, когда параметр является ссылкой lvalue vs rvalue.

Если это не применимо, тогда мы переходим к [over.match.best] /2.5 разрыва связи, предпочитая более специализированный шаблон функции.

Вообще говоря, эмпирическое правило заключается в том, что более конкретное преобразование является лучшим совпадением. Функция преобразования опорных значений lvalue более специфична, чем функция преобразования опорных ссылок, поэтому она предпочтительнее. В том, что intмы инициализируем, нет ничего, что требовало бы значения r (если бы мы вместо этого инициализировали int&&, то operator T&() constкандидат не был бы кандидатом).

Барри
источник
3.2.3 на самом деле говорит, что T&&выигрывает, не так ли? Ах, но у нас нет привязанности к rvalue. Или мы? При выборе наших кандидатов некоторые слова должны были определить, как мы получили, int&и int&&это было обязательным?
Якк - Адам Невраумонт
@ Yakk-AdamNevraumont Нет, тот предпочитает ссылки lvalue ссылкам rvalue. В любом случае, я думаю, что это неправильный раздел, и «более специализированный» тайбрейкер - правильный.
Барри