конечный тип возврата с использованием 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 аргументов, например сумма (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 () работает только с невариантными объявлениями? Если это так, как бы вы написали такую ​​функцию?

 Puppy19 сент. 2010 г., 14:23
Вы забыли сделать правильные ссылки на сумму.
 Johannes Schaub - litb19 сент. 2010 г., 10:36
Это не должно работать в нынешней редакции. Точка объявления функций / переменных и т. Д. Находится после их объявления. Таким образом,sum в конце указанного типа возврата не может найтиsum Шаблон определяется.
 sellibitze19 сент. 2010 г., 15:11
@DeagMG: Точка занята. Но это немного выходит за рамки вопроса.
 sellibitze19 сент. 2010 г., 14:30
@DeadMG, я не вижу проблемы здесь w.r.t. rvalues. Старые добрые ссылки на const обрабатывают значения просто отлично.
 Alex B12 мая 2011 г., 13:49
 sellibitze19 сент. 2010 г., 14:03
@Johannes: Но не просто ли поиск задерживается (до 2-й фазы) из-за зависимости выражения от параметров шаблона?
 sellibitze19 сент. 2010 г., 08:39
Это интересная проблема. Может быть, вы должны спросить в группе Usenet comp.std.c ++, является ли этот вид "рекурсивного вызова" в->decltype(expr) должен работать или нет.
 Johannes Schaub - litb19 сент. 2010 г., 14:13
@sellibitze это хороший момент, но он будет зависеть от типов параметров шаблона, потому что в контексте экземпляра выполняется только поиск, зависящий от аргумента. Если ониint а такжеdouble как здесь, шаблон функции не будет найден. Если среди аргументов есть глобально объявленный класс, глобальныйsum будет найден Так что это довольно «случайно», когда он находит «сумму», он не работает в целом.
 Puppy19 сент. 2010 г., 14:45
@sellibitze: Нет, если вы не отправите их. Затем вы вызовете конструктор копирования и другую семантику lvalue, хотя в C ++ 0x вы можете вызывать семантику перемещения и сохранять кучу производительности. Представьте себе, если его сумма была вызвана на строки. Тогда он будет тратить кучу производительности с lvalues.

Ответы на вопрос(5)

Очевидно, вы не можете использовать 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;
}
 Marti Nito10 дек. 2014 г., 21:30
Для конструктивных типов, отличных от заданных по умолчанию, вышеперечисленное не работает, необходимо заменитьtypedef decltype(T() + typename TypeOfSum<P...>::type()) отtypedef decltype(std::declval<T>() + std::declval<typename TypeOfSum<P...>::type>()) чтобы избежать проблем

Еще один ответ на последний вопрос с меньшим набором текста с использованием C ++ 11std::common_type: Просто используйте

std::common_type<T, P ...>::type

в качестве типа возврата вашей переменной суммы.

относительноstd::common_typeВот выдержка изhttp://en.cppreference.com/w/cpp/types/common_type:

Для арифметических типов общий тип также может рассматриваться как тип арифметического выражения (возможно, смешанного режима), такого как T0 () + T1 () + ... + Tn ().

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

 Marti Nito10 дек. 2014 г., 21:10
Хотя это может сработать, это создает следующую проблему: Предположим, вы хотите вычислить сумму по различным шаблонам выражений. При вашем подходе head + sum (tail ...) не выиграет от оптимизации, которую могут обеспечить шаблоны выражений!
 Marti Nito11 дек. 2014 г., 16:51
Предположим, вы работаете с векторами V матриц M и скалярами s. Тогда он суммирует a1 + a2 + a3 с a1 = sV, a2 = MV и a3 = M.col (1) каждое из этих слагаемых будет иметь различный тип (отражающий основную операцию). Выгодно откладывать оценки и выполнять их в цикле, который требуется для суммы. Если common_type для a1 и a2 является оцененным вектором, то сумма (a1 + a2, a3) будет повторяться дважды, один раз для a1 + a2 и один раз для (a1 + a2) + a3. Конечно, правильная специализация common_type для отражения шаблона выражения операции a1 + a2 + a3 поможет. Hth
 davidhigh11 дек. 2014 г., 02:15
@MartiNito: Я не совсем понимаю, что вы имеете в виду. Во-первых, тип возвращаемого значения - это не что иное, как оптимизация (если она верна, то есть не требует дорогостоящего преобразования или около того). Во-вторых,std::common_type<T...> определяет тип всеT... может быть неявно преобразовано в. То, можете ли вы использовать его в шаблонах выражений, зависит от того, как вы определяете неявные преобразования в них. Но я думаю, что для шаблонов выражений обычно следовало бы явно определить тип возвращаемого значения через рекурсию шаблона (как и в других ответах).
Решение Вопроса

Я думаю, что проблема в том, что шаблон функции 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>()
)>{};
 Maister19 сент. 2010 г., 13:31
Это действительно работает. Я не совсем понимаю шаблон <class T, class ... P> struct sum_type; хоть. Будет ли он просто использовать шаблон <класс T, класс U, класс ... P> версию?
 sellibitze19 сент. 2010 г., 13:57
@Maister: это правильно.
 Alex B12 мая 2011 г., 13:25
не былоdecltype на самом деле предназначены для замены неестественных конструкций, как это? Я действительно надеюсь, что это просто ошибка GCC, хотя я использую 4.5.3, и она все еще там.
 Maister19 сент. 2010 г., 13:55
Понятно, посмотрим, правильно ли я понял. Таким образом, каждый раз создается экземпляр sum_type, template <class T, class ... P> sum_type; используется, но поскольку есть специализации sum_type <T> и sum_type <T, U, P ...>, эти специализации будут использоваться вместо этого, и, таким образом, нет необходимости фактически определять тело шаблона <class T, class ... P> struct sum_type; ?
 sellibitze19 сент. 2010 г., 13:42
@Maister, первая специализация - для одного аргумента, а вторая - как минимум для двух аргументов (P может быть пустым пакетом параметров). Но подход Tomaka17, похоже, тоже работает. Есть одно небольшое отличие, хотя. Моя версия дает вам decltype ((a + b) + c), а версия Tomaka17 дает вам decltype (a + (b + c)). Если вы работаете со странными пользовательскими типами, это может иметь значение.
 Marti Nito10 дек. 2014 г., 21:32
Я уже прокомментировал решение Tomaka17, я думаю, что для вашего решения существует такая же проблема, заменивdecltype( val<const T&>() + val<const U&>() ) сdecltype( std::declval<T>() + std::declval<U>() ) должен решить проблему

Я предоставляю это улучшение принятому ответу. Всего две структуры

#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

 tjwrona199206 дек. 2018 г., 19:32
Просто вопрос из любопытства, почему здесь так важен порядок? Я попытался написать простой пример и понял, что если вы поменяете местами порядок двух определений структуры, он больше не будет компилироваться. (другими словами, если вы положитеtemplate <typename P> определение структуры передtemplate <typename P, typename... Ps> определение структуры.)
 smac8906 дек. 2018 г., 19:41
@ tjwrona1992 Второе определение структуры - это специализация первого. Увидетьpartial template specialization.

Решение C ++ 14:

template <class T, class... P>
auto sum(const T& t, const P&... p){
    return t + sum(p...);
}

Тип возврата вычитается автоматически.

Смотрите это в онлайн-компиляторе

Ваш ответ на вопрос