У меня есть общий вопрос о шаблонных функциях против автоматического вывода типа для функций.
В течение многих лет мы были в состоянии написать шаблонную функцию:
template <class T> T add(T a,T b){
return a+b;
}
Есть TS для использования авто для вывода параметров функции
auto add(auto a,auto b){
return a+b;
}
Хотя с помощью auto никто не мог добраться до реального типа и, например, использовать статические члены, но это прекрасно работает:
#include <iostream>
struct foo
{
static void bar(){std::cout<<"bar"<<std::endl;}
static int i ;
};
int foo::i{0};
void t(auto f){
decltype(f)::bar();
std::cout<< decltype(f)::i<<std::endl;
}
int main(int argc, char *argv[])
{
t(foo());
return 0;
}
Так есть ли причина выбирать один вместо другого?
Очевидная причина для этого конкретного кода состоит в том, что они на самом деле не имеют одинаковую семантику вообще.
В частности, если вы передаете аргументы разных типов, версия использует auto
будет выводить тип независимо для каждого, затем на основе тех, которые выводят тип для результата.
И наоборот, в версии шаблона вы указали ровно один тип, поэтому аргументы должны быть одного типа (и результат будет одинаковым).
Итак, чтобы код был более близким к эквивалентному, вам действительно нужно написать шаблон более похожим на:
template <class T, class U>
auto add(T a, U b) -> decltype(a+b) {
return a+b;
}
Это, очевидно, также возможно, но добавляет еще больше силы аргументам в пользу использования auto
вместо.
В вашем коде есть два разных использования auto
, один в параметрах и другой в типе возврата. В случае параметров, каждое использование auto
вводит уникальный аргумент типа шаблона, поэтому, как упоминал Джерри, он будет эквивалентен:
// 1
template <typename A, typename B>
auto add(A a, B b) {
return a + b;
}
В этом случае, если у вас есть какое-либо ограничение на аргументы другого типа (должно быть одно и то же, но может быть и другое), то явный синтаксис шаблона обеспечивает лучшую альтернативу. Это особенно важно, если вы хотите использовать SFINAE для аргументов (посредством дополнительного аргумента шаблона):
// 2
template <typename A, typename B,
typename _1 = typename A::iterator, // A has nested iterator type
typename _2 = typename B::iterator> // So does B
auto f(A a, B b);
Обратите внимание, что я намеренно избегал использования SFINAE для возвращаемого типа, так как это как-то мешает вашему другому использованию auto
, но это может быть другой вариант:
// 3
auto f(auto a, auto b)
-> typename enable_if<has_nested_iterator<decltype(a)>::value
&& has_nested_iterator<decltype(b)>::value,
[return type] >::type;
Но, как вы можете видеть, он становится немного более сложным, так как вам нужно использовать конечный тип возврата и получить тип через значение с помощью decltype
,
Второе использование auto
в вашем примере, который сильно отличается от этого, находится в типе возврата, чтобы иметь выведенный тип возврата. Выведенный тип возврата — это функция, которая уже была доступна в C ++ 11 для лямбд, но была распространена на все шаблоны функций. Функция позволяет компилятору находить тип, возвращаемый функцией, проверяя различные return
заявления внутри тела. Преимущество состоит в том, что если ваш шаблон имеет одно возвращаемое выражение, вы можете избежать необходимости вводить это выражение дважды: одно для возвращаемого типа, другое для фактического кода:
// 4
auto add(auto a, auto b) -> decltype(a + b) { // 'a + b' here
return a + b; // 'a + b' also here
}
Недостатком является то, что компилятор должен проверять тело функции, чтобы определить тип, который будет возвращен, и это обязательно после подстановки типа. Таким образом, оператор возврата из функции, имеющей выведенный тип, не может быть использован в выражении SFINAE, что потенциально усложняет жизнь пользователям вашей функции:
// 5
auto doAdd(auto a, auto b)
-> typename enable_if<is_integral<decltype(add(a,b))>>::type
{
return add(a,b);
}
СФИНА будет не удалить выше doAdd
перегрузка из набора разрешений перегрузки, вызывающая серьезную ошибку, если вы называете это как doAdd(1, 1.)
,
Подумав, единственная причина, по которой я вижу использование шаблонной функции, — это принудительное использование нескольких параметров одного типа. В то время как с авто, вычитание каждого типа не зависит друг от друга.
Вы даже можете смешать template и auto, если хотите, чтобы первый и третий параметры имели одинаковый тип и общий тип для второго.
template <class T> void barb(T a,auto b,T c){}
int main(int argc, char *argv[])
{
barb(5," ",5); //ok
barb(5," ",5.6); //fails
return 0;
}
Как пишет Oktalist в комментарии, вы можете использовать оператор using, чтобы получить тип параметра auto.