Конечный тип возврата с использованием decltype с функцией шаблона variadic
Я хочу написать простой сумматор (для смеха), который складывает каждый аргумент и возвращает сумму с соответствующим типом. В настоящее время у меня есть это:
#include <iostream>
using namespace std;
template <class T>
T sum(const T& in)
{
return in;
}
template <class T, class... P>
auto sum(const T& t, const P&... p) -> decltype(t + sum(p...))
{
return t + sum(p...);
}
int main()
{
cout << sum(5, 10.0, 22.2) << endl;
}
В GCC 4.5.1 это, кажется, работает отлично для 2 аргументов, например, sum(2, 5.5) возвращает 7.5. Однако, с большим количеством аргументов, чем это, я получаю ошибки, что sum () просто еще не определена. Однако, если я объявлю sum () следующим образом:
template <class T, class P...>
T sum(const T& t, const P&... p);
Тогда это работает для любого числа аргументов, но sum(2, 5.5) вернет целое число 7, что я не ожидал. Имея более двух аргументов, я предполагаю, что decltype () должен будет выполнить какую-то рекурсию, чтобы иметь возможность определить тип t + sum(p...). Это законно C++0x? или decltype () работает только с невариантными объявлениями? Если это так, как бы вы написали такую функцию?
5 ответов
Я думаю, что проблема в том, что шаблон функции variadic считается объявленным только после того, как вы указали его тип возвращаемого значения, так что sum
в decltype
никогда не может ссылаться на сам шаблон функции variadic. Но я не уверен, что это ошибка GCC или C++0x просто не позволяет этого. Я предполагаю, что C++0x не допускает "рекурсивный" вызов в ->decltype(expr)
часть.
В качестве обходного пути мы можем избежать этого "рекурсивного" вызова в ->decltype(expr)
с пользовательским классом черт:
#include <iostream>
#include <type_traits>
using namespace std;
template<class T> typename std::add_rvalue_reference<T>::type val();
template<class T> struct id{typedef T type;};
template<class T, class... P> struct sum_type;
template<class T> struct sum_type<T> : id<T> {};
template<class T, class U, class... P> struct sum_type<T,U,P...>
: sum_type< decltype( val<const T&>() + val<const U&>() ), P... > {};
Таким образом, мы можем заменить decltype
в вашей программе с typename sum_type<T,P...>::type
и это скомпилируется.
Изменить: так как это на самом деле возвращает decltype((a+b)+c)
вместо decltype(a+(b+c))
что было бы ближе к тому, как вы используете сложение, вы можете заменить последнюю специализацию на это:
template<class T, class U, class... P> struct sum_type<T,U,P...>
: id<decltype(
val<T>()
+ val<typename sum_type<U,P...>::type>()
)>{};
Решение C++14:
template <class T, class... P>
auto sum(const T& t, const P&... p){
return t + sum(p...);
}
Тип возврата вычитается автоматически.
Очевидно, вы не можете использовать decltype рекурсивным способом (по крайней мере, на данный момент, может быть, они это исправят)
Вы можете использовать структуру шаблона, чтобы определить тип суммы
Это выглядит некрасиво, но это работает
#include <iostream>
using namespace std;
template<typename... T>
struct TypeOfSum;
template<typename T>
struct TypeOfSum<T> {
typedef T type;
};
template<typename T, typename... P>
struct TypeOfSum<T,P...> {
typedef decltype(T() + typename TypeOfSum<P...>::type()) type;
};
template <class T>
T sum(const T& in)
{
return in;
}
template <class T, class... P>
typename TypeOfSum<T,P...>::type sum(const T& t, const P&... p)
{
return t + sum(p...);
}
int main()
{
cout << sum(5, 10.0, 22.2) << endl;
}
Еще один ответ на последний вопрос с меньшим набором текста с использованием C++11 std::common_type
: Просто используйте
std::common_type<T, P ...>::type
в качестве типа возврата вашей переменной суммы.
относительно std::common_type
Вот выдержка из http://en.cppreference.com/w/cpp/types/common_type:
Для арифметических типов общий тип также может рассматриваться как тип арифметического выражения (возможно, смешанного режима), такого как T0() + T1() + ... + Tn().
Но очевидно, что это работает только для арифметических выражений и не решает общую проблему.
Я предоставляю это улучшение принятому ответу. Всего две структуры
#include <utility>
template <typename P, typename... Ps>
struct sum_type {
using type = decltype(std::declval<P>() + std::declval<typename sum_type<Ps...>::type>());
};
template <typename P>
struct sum_type<P> {
using type = P;
};
Теперь просто объявите ваши функции как
template <class T>
auto sum(const T& in) -> T
{
return in;
}
template <class P, class ...Ps>
auto sum(const P& t, const Ps&... ps) -> typename sum_type<P, Ps...>::type
{
return t + sum(ps...);
}
С этим ваш тестовый код теперь работает
std::cout << sum(5, 10.0, 22.2, 33, 21.3, 55) << std::endl;
146,5
Правильный способ сделать:
#include <utility>
template <typename... Args>
struct sum_type;
template <typename... Args>
using sum_type_t = typename sum_type<Args...>::type;
template <typename A>
struct sum_type<A> {
using type = decltype( std::declval<A>() );
};
template <typename A, typename B>
struct sum_type<A, B> {
using type = decltype( std::declval<A>() + std::declval<B>() );
};
template <typename A, typename B, typename... Args>
struct sum_type<A, B, Args...> {
using type = sum_type_t< sum_type_t<A, B>, Args... >;
};
template <typename A>
sum_type_t<A> sum(A &&a)
{
return (std::forward<A>(a));
}
template <typename A, typename B>
sum_type_t<A, B> sum(A &&a, B &&b)
{
return (std::forward<A>(a) + std::forward<B>(b));
}
template <typename A, typename B, typename... C>
sum_type_t<A, B, C...> sum(A &&a, B &&b, C &&...args)
{
return sum( sum(std::forward<A>(a), std::forward<B>(b)), std::forward<C>(args)... );
}
https://coliru.stacked-crooked.com/a/a5a0e8019e40b8ba
Это полностью сохраняет результирующий тип операций (даже ссылку на r-значение). Порядок действий естественный:
(((a+b)+c)+d)
.
Для C++17:
template <class... P>
auto sum(const P... p){
return (p + ...);
}
int main()
{
std::cout << sum(1, 3.5, 5) << std::endl;
return EXIT_SUCCESS;
}
Прочтите о складных выражениях.