Выведение аргумента шаблона класса для параметра, зависящего от шаблона
Начнем с простого метода добавления для класса number
:
class number {
int num;
public:
number(int num = 0): num(num) {}
operator int() const { return num; }
};
number add(number t1, number t2) {
return t1 + t2;
}
int main() {
auto result1 = add(1, 2); // auto-casting, works fine
}
Теперь мы хотим повернуть number
в класс шаблона:
template<class T>
class number {
T num;
public:
number(T num = 0): num(num) {}
operator T() const { return num; }
};
template<class T>
number<T> add(number<T> t1, number<T> t2) {
return t1 + t2;
}
Пытаюсь позвонить add
так же, как мы назвали простой без шаблонов, основанный (теоретически!) на CTAD:
int main() {
number a = 3; // works, using CTAD
// auto result1 = add(1, 2); // <== what we wish for, but can't find method
// auto result2 = add(a, 2); // this also doesn't work, no ADL here :(
auto result3 = add<int>(1, 2); // this works, but is not what we wish
}
Обратите внимание, что если add
была функцией друга, вызывая ее с одним из параметров, являющимся number
будет работать на основе ADL:
template<class T>
class number {
T num;
public:
number(T num = 0): num(num) {}
operator T() const { return num; }
friend number add(number t1, number t2) {
return t1 + t2;
}
};
int main() {
number a = 3; // works, using CTAD
auto result1 = add(a, 2); // works, based on ADL
// auto result2 = add(1, 2); // still doesn't work, no ADL here :(
}
Любое предложение, как разрешить классу шаблона вести себя так же, как не-шаблон, с автоматическим преобразованием при вызове добавления?
РЕДАКТИРОВАТЬ: этот вопрос был отредактирован на основе опубликованных комментариев. Следует подчеркнуть, что для такой универсальной функции, какadd
иметь такое автоматическое приведение может быть неправильной идеей, но предположим, что метод очень специфичен, что-то вроде doSomethingWithNumbers
.
2 ответа
Думаю, ответ прост. В первом случае с бесплатным шаблоном функции в первую очередь срабатывает разрешение перегрузки и вывод аргументов шаблона функции. Поскольку компилятор не может обнаружитьT
из int
передается как аргумент (clang говорит:candidate template ignored: could not match 'number<type-parameter-0-0>' against 'int'
), разрешение перегрузки не удается и программа плохо сформирована.
Когда функция определяется как friend
, это не шаблонная функция. Компилятор создал его при создании экземпляра класса (дляnumber<int>
; первая строка вmain
). Теперь, когда он его находит (с помощью ADL), типы параметров уже установлены (обаnumber<int>
потому что это исходит от number<int>
создание экземпляра), и осталось решить, как преобразовать переданный аргумент из int
к number<int>
, где используется неявное преобразование (совпадающим c-tor). Здесь тоже нет CTAD.
Похожий (но не совсем тот же) случай обсуждается Скоттом Мейерсом в Эффективном C++ (3-е издание), Правило 46: Определение функций, не являющихся членами внутри шаблонов, когда требуется преобразование типов.
Изменить: чтобы ответить на вопрос, нельзя смешивать вывод аргументов шаблона функции и неявное преобразование типа для аргументов. Выбери один. (Это то, что объясняет Мейерс в упомянутом пункте.)
В строке комментария @Taekahn мы можем добиться желаемого поведения, хотя это не автоматическое приведение типов:
// the additional `requires` on number is not mandatory for the example
// but it is reasonable that number would be restricted
template<class T> requires std::integral<T> || std::floating_point<T>
class number {
T num;
public:
number(T num = 0): num(num) {}
operator T() const { return num; }
};
template<typename T>
concept Number = requires(T t) {
number(std::move(t)); // anything that a number can be created from
// that includes number itself
};
auto add(Number auto t1, Number auto t2) {
return number{std::move(t1)} + number{std::move(t2)};
}
int main() {
number a = 3;
auto result1 = add(1, 2);
auto result2 = add(a, 2);
auto result3 = add<double>(1, 2);
}