Почему экземпляры шаблонов не могут быть выведены в `std:: reference_wrapper`?
Предположим, что у меня есть некоторый объект типа T
, и я хочу поместить его в ссылочную оболочку:
int a = 5, b = 7;
std::reference_wrapper<int> p(a), q(b); // or "auto p = std::ref(a)"
Теперь я могу легко сказать if (p < q)
, потому что эталонная оболочка имеет преобразование в свой завернутый тип. Все счастливы, и я могу обработать набор ссылочных оберток, как и исходные объекты.
(Как показывает приведенный ниже вопрос, приведенный ниже, это может быть полезным способом для создания альтернативного представления существующей коллекции, которая может быть переупорядочена по желанию без каких-либо затрат на полную копию, а также с сохранением целостности обновления с исходной коллекцией.)
Однако, с некоторыми классами это не работает:
std::string s1 = "hello", s2 = "world";
std::reference_wrapper<std::string> t1(s1), t2(s2);
return t1 < t2; // ERROR
Мое обходное решение - определить предикат , как в этот ответ *; но мой вопрос:
Почему и когда операторы могут применяться к ссылочным оберткам и прозрачно использовать операторов обернутых типов? Почему это не работает для std::string
? Что это связано с тем, что std::string
является экземпляром шаблона?
*) Обновление. В свете ответов кажется, что использование std::less<T>()
является общим решением.
Ответы
Ответ 1
Изменить: Переместил мои догадки на дно, здесь идет нормативный текст, почему это не сработает. Версия TL: DR:
Разрешения конверсий не допускаются, если параметр функции содержит параметр выведенного шаблона.
§14.8.3 [temp.over] p1
[...] Когда вызов этого имени записывается (явно или неявно с использованием оператора нотация), вывод аргумента шаблона (14.8.2) и проверка любых явных аргументов шаблона (14.3) для каждого шаблона функции, чтобы найти значения аргумента шаблона (если они есть), которые можно использовать с этим шаблоном функции для создания экземпляра шаблона функции специализацию, которая может быть вызвана с помощью аргументов вызова.
§14.8.2.1 [temp.deduct.call] p4
[...] [Примечание: как указано в 14.8.1, неявные преобразования будут выполняться на аргументе функции, чтобы преобразовать его в тип соответствующего параметра функции , если Параметр не содержит шаблонных параметров, которые участвуют в выводе аргумента шаблона. [...] -end note]
§14.8.1 [temp.arg.explicit] p6
Неявные преобразования (раздел 4) будут выполняться на аргументе функции, чтобы преобразовать его в тип соответствующего параметра функции, если тип параметра не содержит шаблонных параметров, которые участвуют в выводе аргумента шаблона. [Примечание. Параметры шаблона не участвуют в выводе аргумента шаблона, если они явно указаны. [...] -end note]
Так как std::basic_string
зависит от выведенных параметров шаблона (CharT
, Traits
), конверсии не допускаются.
Это своего рода проблема с курицей и яйцом. Чтобы вывести аргумент шаблона, ему нужен фактический экземпляр std::basic_string
. Чтобы преобразовать в завернутый тип, необходима цель преобразования. Эта цель должна быть фактическим типом, который не является шаблоном класса. Компилятор должен будет проверить все возможные экземпляры std::basic_string
для оператора преобразования или что-то в этом роде, что невозможно.
Предположим, что следующий минимальный тест:
#include <functional>
template<class T>
struct foo{
int value;
};
template<class T>
bool operator<(foo<T> const& lhs, foo<T> const& rhs){
return lhs.value < rhs.value;
}
// comment this out to get a deduction failure
bool operator<(foo<int> const& lhs, foo<int> const& rhs){
return lhs.value < rhs.value;
}
int main(){
foo<int> f1 = { 1 }, f2 = { 2 };
auto ref1 = std::ref(f1), ref2 = std::ref(f2);
ref1 < ref2;
}
Если мы не обеспечиваем перегрузку для экземпляра на int
, вывод не выполняется. Если мы предоставим эту перегрузку, это то, что компилятор может протестировать против одного разрешенного пользователем преобразования (foo<int> const&
является целью преобразования). Поскольку преобразование соответствует в этом случае, разрешение перегрузки преуспевает, и мы получили вызов нашей функции.
Ответ 2
std::reference_wrapper
не имеет operator<
, поэтому единственный способ сделать ref_wrapper<ref_wrapper
- через член ref_wrapper
:
operator T& () const noexcept;
Как вы знаете, std::string
:
typedef basic_string<char> string;
Соответствующая декларация для string<string
:
template<class charT, class traits, class Allocator>
bool operator< (const basic_string<charT,traits,Allocator>& lhs,
const basic_string<charT,traits,Allocator>& rhs) noexcept;
Для string<string
этот шаблон объявления этой функции создается путем сопоставления string
= basic_string<charT,traits,Allocator>
, который разрешает charT
= char
и т.д.
Поскольку std::reference_wrapper
(или любой из его (нулевых) классов баз) не может соответствовать basic_string<charT,traits,Allocator>
, шаблон объявления функции не может быть создан в объявлении функции и не может участвовать в перегрузке.
Здесь важно, что существует прототип no.
Минимальный код, показывающий проблему
template <typename T>
class Parametrized {};
template <typename T>
void f (Parametrized<T>);
Parametrized<int> p_i;
class Convertible {
public:
operator Parametrized<int> ();
};
Convertible c;
int main() {
f (p_i); // deduce template parameter (T = int)
f (c); // error: cannot instantiate template
}
Дает:
In function 'int main()':
Line 18: error: no matching function for call to 'f(Convertible&)'
Стандартные ссылки
14.8.2.1 Вывод аргументов шаблона из вызова функции [temp.deduct.call]
Вывод аргумента шаблона производится путем сравнения каждого параметра параметра шаблона функции (вызывайте его P
) с типом соответствующего аргумента вызова (назовите его A
), как описано ниже.
(...)
В целом процесс дедукции пытается найти значения аргументов шаблона, которые сделают вывод A
идентичным A
(после преобразования типа A
, как описано выше). Однако есть три случая, которые позволяют разницу:
- Если исходный
P
является ссылочным типом, выводимый A
(т.е. тип, на который ссылается ссылка) может быть больше CV, чем преобразованный A
.
Обратите внимание, что это имеет место с std::string()<std::string()
.
- Преобразованный
A
может быть другим указателем или указателем на тип члена, который может быть преобразован в выведенный A
через квалификационное преобразование (4.4).
См. комментарий ниже.
- Если
P
является классом, а P
имеет форму simple-template-id, преобразованный A
может быть производным классом выведенного A
.
Комментарий
Это означает, что в этом параграфе:
14.8.1 Явная спецификация аргументов шаблона [temp.arg.explicit]/6
Неявные преобразования (раздел 4) будут выполняться на аргументе функции, чтобы преобразовать его в тип соответствующего параметра функции , если тип параметра не содержит шаблонных параметров, которые участвуют в выводе аргумента шаблона.
, если не следует воспринимать как , если и только если, поскольку это прямо противоречит цитированному ранее тексту.