Ответ 1
Решение уже показано в этом ответе. Теперь, больше о проблеме...
Проблема вашего кода заключается в том, как выполняется разрешение перегрузки. Когда рассматривается функция шаблона для разрешения перегрузки, компилятор будет выполнять вывод типа по аргументам и придумывать подмену типа, которая соответствует вызову, или же он не может применить этот шаблон, удаляет его из набора потенциальных кандидатов и продолжается. Проблема на этом этапе заключается в том, что вывод типа выводит только точные соответствия (с возможной дополнительной константой/неустойчивой квалификацией). Поскольку соответствие является точным, компилятор не будет использовать какое-либо преобразование (опять же, кроме cv).
Самый простой пример этого происходит с функциями std::max
и std::min
:
unsigned int i = 0;
std::min( i, 10 ); // Error!
Вывод типа выводит T в template <typename T> min( T const &, T const & )
как unsigned
для первого аргумента, но int
для второго они отличаются и компилятор отбрасывает эту функцию шаблона.
Решение, предложенное в ответе , использует функцию языка, которая позволяет вам определять функцию друга, не являющуюся членом, внутри определения класса. Преимущество шаблонов заключается в том, что для каждого (различного) создания шаблона компилятор создаст свободную функцию без шаблона на уровне пространства имен, которая имеет подпись, полученную путем подстановки реальных типов экземпляра в объявлении друга:
template <typename T>
class test {
friend test operator+( test const & lhs, test const & rhs ) { // [1]
return test();
}
}
test<int> t; // [2]
В приведенном выше примере компилятор позволяет добавить определение функции friend внутри области класса в [1]. Затем, когда вы создаете шаблон в [2], компилятор будет генерировать свободную функцию:
test<int> operator+( test<int> const & lhs, test<int> const & rhs ) {
return test<int>();
}
Функция определяется всегда, независимо от того, используете вы ее или нет (это отличается от функций-членов класса шаблонов, которые создаются по требованию).
Магия здесь имеет несколько сторон. Первая часть состоит в том, что она в целом вы определяете функции без шаблонов для каждого и всех созданных типов, поэтому вы получаете универсальность и в то же время преимущество разрешения перегрузки, способного использовать эту функцию когда аргументы не идеальны.
Поскольку это не шаблонная функция, компилятор может вызывать неявные преобразования для обоих аргументов, и вы получите ожидаемое поведение.
Кроме того, при поиске выполняется другой тип магии, так как определенная функция может быть найдена только в зависимости от зависимостей, зависящих от аргумента, если она также не объявлена на уровне пространства имен, что в нашем случае не может быть сделано общим способом. Импликация этого может быть хорошей или плохой, в зависимости от того, как вы хотите ее рассмотреть...
Поскольку он может быть найден только ADL, он не будет рассматриваться, если хотя бы один из аргументов уже имеет нужный тип (т.е. он никогда не будет использоваться для преобразования обоих аргументов). Недостатком является то, что невозможно ссылаться на функцию, если вы на самом деле ее не вызываете, а это означает, что вы не можете получить указатель на функцию.
(Подробнее о шаблоне дружбы здесь, но обратите внимание, что в этом конкретном случае все остальные варианты не смогут выполнять неявные преобразования).