Trailing return type с использованием метода decltype с функцией вариационного шаблона

Я хочу написать простой сумматор (для хихиканья), который добавляет каждый аргумент и возвращает сумму с соответствующим типом. В настоящее время у меня есть это:

#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 аргументов, например. сумма (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...). Является ли это законным С++ 0x? или decltype() работает только с невариантными объявлениями? Если это так, как бы вы пишете такую ​​функцию?

Ответ 1

Я думаю, проблема в том, что шаблон вариационной функции считается объявленным только после того, как вы указали его тип возврата, чтобы sum в decltype никогда не ссылался на сам шаблон вариационной функции. Но я не уверен, является ли это ошибкой GCC или С++ 0x просто не позволяет этого. Я предполагаю, что С++ 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>()
)>{};

Ответ 2

По-видимому, вы не можете использовать 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;
}

Ответ 4

Другой ответ на последний вопрос с меньшим набором текста с помощью С++ 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().

Но, очевидно, это работает только для арифметических выражений и не излечивает общую проблему.