Выберите между функцией шаблона и автоматическим вычетом типа

У меня есть общий вопрос о шаблонных функциях против автоматического вывода типа для функций.

В течение многих лет мы были в состоянии написать шаблонную функцию:

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;
}

Так есть ли причина выбирать один вместо другого?

3

Решение

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

В частности, если вы передаете аргументы разных типов, версия использует auto будет выводить тип независимо для каждого, затем на основе тех, которые выводят тип для результата.

И наоборот, в версии шаблона вы указали ровно один тип, поэтому аргументы должны быть одного типа (и результат будет одинаковым).

Итак, чтобы код был более близким к эквивалентному, вам действительно нужно написать шаблон более похожим на:

template <class T, class U>
auto add(T a, U b) -> decltype(a+b) {
return a+b;
}

Это, очевидно, также возможно, но добавляет еще больше силы аргументам в пользу использования auto вместо.

5

Другие решения

В вашем коде есть два разных использования 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.),

3

Подумав, единственная причина, по которой я вижу использование шаблонной функции, — это принудительное использование нескольких параметров одного типа. В то время как с авто, вычитание каждого типа не зависит друг от друга.

Вы даже можете смешать 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.

0
По вопросам рекламы ammmcru@yandex.ru
Adblock
detector