Почему экземпляры шаблона не могут быть выведены в `std :: reference_wrapper`s? - PullRequest
16 голосов
/ 15 декабря 2011

Предположим, у меня есть какой-то объект типа 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>() является общим решением.

Ответы [ 2 ]

7 голосов
/ 15 декабря 2011

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

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


§14.8.3 [temp.over] p1

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

§14.8.2.1 [temp.deduct.call] p4

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

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

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

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

...