Почему нельзя вывести экземпляры шаблона в `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>() является общим решением.


person Kerrek SB    schedule 14.12.2011    source источник
comment
Я подозреваю, что это связано с тем, что рассматриваемый оператор является свободной шаблонной функцией. Поскольку входными данными в настоящее время являются reference_wrapper‹basic_string‹char, std::char_traits‹char› ›, а не какая-либо специализация basic_string‹ CharT, CharTraits ›, функция шаблона не рассматривается и недоступна для выполнения неявных преобразований. Однако мои стандарты-фу довольно слабы, и я оставлю это тем, кто более знаком с ответом.   -  person Dave S    schedule 15.12.2011
comment
@DaveS: я думаю, ты на правильном пути. Подробный ответ на этот счет был бы высоко оценен.   -  person Kerrek SB    schedule 15.12.2011


Ответы (2)


Редактировать: мои догадки перенесены вниз, здесь идет нормативный текст, почему это не сработает. Версия TL;DR:

Преобразования не допускаются, если параметр функции содержит выведенный параметр шаблона.


§14.8.3 [temp.over] p1

[...] Когда вызов этого имени записывается (явно или неявно с использованием обозначения оператора), для каждого шаблона функции выполняется вывод аргумента шаблона (14.8.2) и проверка любых явных аргументов шаблона (14.3). значения аргументов шаблона (если есть), которые можно использовать с этим шаблоном функции для создания экземпляра специализации шаблона функции, которая может быть вызвана с помощью аргументов вызова.

§14.8.2.1 [temp.deduct.call] p4

[...] [ Примечание: как указано в 14.8.1, неявные преобразования будут выполняться для аргумента функции, чтобы преобразовать его в тип соответствующего параметра функции < strong>если параметр не содержит параметров-шаблонов, которые участвуют в выводе аргументов шаблона. [...] —конец примечания ]

§14.8.1 [temp.arg.explicit] p6

Неявные преобразования (пункт 4) будут выполняться для аргумента функции, чтобы преобразовать его в тип соответствующего параметра функции, если тип параметра не содержит параметров шаблона, которые участвуют в выводе аргумента шаблона. [ Примечание. Параметры шаблона не участвуют в выводе аргументов шаблона, если они указаны явно. [...] —конец примечания ]

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

person Xeo    schedule 14.12.2011
comment
Пример или контрпример того, где дедукция не работает (и как это связано с тем, что string является экземпляром шаблона, в отличие от int), был бы столь же полезен! :-) - person Kerrek SB; 15.12.2011
comment
@Kerrek: добавлен небольшой пример, все еще ищу что-то, что можно процитировать из стандарта (я просто не доволен без него по такого рода вопросам). - person Xeo; 15.12.2011
comment
@Kerrek: Вот стандартные кавычки. :) - person Xeo; 15.12.2011
comment
[Примечание: как указано в 14.8.1, неявные преобразования будут выполняться для аргумента функции, чтобы преобразовать его в тип соответствующего параметра функции, если параметр не содержит параметров шаблона, которые участвуют в выводе аргумента шаблона. [...] —конец примечания ] интересно, но это только информативный текст. (Это недействительно, если не подкреплено нормативным текстом.) - person curiousguy; 15.12.2011
comment
@curiousguy: Это есть резервная копия, это цитата прямо ниже в моем ответе. - person Xeo; 15.12.2011
comment
Неявные преобразования (пункт 4) Вопрос не о Стандартных преобразованиях, а о неявных преобразованиях. Цитата неуместна. - person curiousguy; 15.12.2011
comment
@curiousguy: [Примечание: для пользовательских типов также учитываются пользовательские преобразования; см. 12.3. Как правило, неявная последовательность преобразования (13.3.3.1) состоит из стандартной последовательности преобразования, за которой следует определяемое пользователем преобразование, за которым следует другая стандартная последовательность преобразования. —конец примечания] §4 [conv] p4. - person Xeo; 15.12.2011
comment
Судя по вашему прочтению цитируемого стандартного пункта, этот пункт неверен. Насколько я понимаю, этот пункт просто бесполезен. - person curiousguy; 15.12.2011
comment
Может быть... в любом случае, пункт читается как blablabla, если blobloblo blobloblo не применяется, поэтому и blablabla не применяется. - person curiousguy; 15.12.2011
comment
Это предложение находится не в том месте (многие люди просто не будут искать в [temp.arg.**explicit**] правила о вызовах без явных параметров шаблона), это предложение ссылается на неправильный раздел ((пункт 4) должен быть 13.3.3.1 Неявные последовательности преобразования), это предложение сбивает с толку (мы интерпретируем его по-разному, даже вне контекста [temp.arg.explicit]) и это предложение избыточно (если оно правильное) . Что еще? - person curiousguy; 15.12.2011
comment
@curi какой текст ты ищешь? В этой области была проделана работа в С++ 11. - person Johannes Schaub - litb; 15.12.2011
comment
Fwiw я согласен, что обсуждаемое правило не несет ответственности за диагностику - person Johannes Schaub - litb; 15.12.2011
comment
@JohannesSchaub-litb Сейчас я ничего не ищу, спасибо. - person curiousguy; 16.12.2011
comment
@Johannes: Почему бы и нет? О чем еще может говорить правило? - person Xeo; 16.12.2011

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

Здесь важно то, что не существует никакого нешаблонного operator< (string, string) прототипа.

Минимальный код, показывающий проблему

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) будут выполняться для аргумента функции, чтобы преобразовать его в тип соответствующего параметра функции, если тип параметра не содержит параметров шаблона, которые участвуют в выводе аргумента шаблона.

if не следует воспринимать как если и только если, так как это будет прямо противоречить тексту, процитированному ранее.

person curiousguy    schedule 14.12.2011