Как написать универсальную версию класса шаблона Variadic, который вычисляет сумму целых чисел - PullRequest
3 голосов
/ 23 мая 2019

Я встретил следующий код (ниже) на этом сайте

https://www.modernescpp.com/index.php/c-insights-variadic-templates

Но это объявление / определение работает только с целыми числами, и я хочу написать версию, которая работает с другими типами, такими как float, double, std :: strings и определенными пользователем типами, которые перегружают оператор "+". Однако я изо всех сил пытаюсь написать один.

Обратите внимание, что на упомянутом сайте есть решение на основе шаблонов переменных функций, которое работает с различными типами данных, включая типы с плавающей запятой (еще не опробовано с пользовательскими типами). Я ищу решение, основанное на шаблонном классе. Это чисто для учебной цели.

Может кто-нибудь помочь мне понять это правильно?

#include<iostream>

template<int...>
struct add;

template<>
struct add<>
{
  static constexpr int value = 0;
};

template<int i, int... tail>
struct add<i, tail...>
{
  static constexpr int value = i + add<tail...>::value;
};

int main()
{
    std::cout << add<1,2,3,4>::value;
}

Я написал это, но застрял

template<typename ...>
struct add;

template<typename T, typename... args>
struct add<T, args...>
{
    static constexpr T value = ??//T();//what to write here?
};

Заранее спасибо.

Ответы [ 3 ]

2 голосов
/ 23 мая 2019

Как насчет чего-то следующего?

#include <iostream>

template <typename T, T...>
struct add
{ static constexpr T value = 0; };

template <typename T, T head, T ... tail>
struct add<T, head, tail...>
{ static constexpr T value = head + add<T, tail...>::value; };

int main()
 {
    std::cout << add<int, 1, 2, 3, 4>::value << std::endl;
    std::cout << add<long, 10l, 20l, 30l, 40l>::value << std::endl;
 }

Или, может быть, лучше, наследуя от std::integral_constant

template <typename T, T...>
struct add : public std::integral_constant<T, T{0}>
{ };

template <typename T, T head, T ... tail>
struct add<T, head, tail...>
   : public std::integral_constant<T, head + add<T, tail...>::value>
{ };

Если вы можете использовать C ++ 17, вам больше не нужна рекурсия, но вы можете использовать свертывание шаблонов.

template <typename T, T... Is>
struct add : public std::integral_constant<T, (... + Is)>
 { };

C ++ 17 также предлагает вам возможность избавиться от аргумента типа typename T, используя auto для значений.

Проблема становится такой: какой тип является value, учитывая, что значения шаблона могут быть разных типов?

Я полагаю, что std::common_type может быть решением этого вопроса, поэтому

#include <iostream>
#include <type_traits>

template <auto ... Is>
struct add :
   public std::integral_constant<std::common_type_t<decltype(Is)...>,
                                 (... + Is)>
 { };

int main()
 {
    std::cout << add<1, 2, 3, 4>::value << std::endl;
    std::cout << add<10l, 20l, 30l, 40l>::value << std::endl;
 }

или, может быть, просто используя decltype((... + Is))

template <auto ... Is>
struct add :
   public std::integral_constant<decltype((... + Is)), (... + Is)>
 { };

Не по теме: оригинал add можно немного упростить следующим образом

template <int...>
struct add
 { static constexpr int value = 0; };

template <int i, int... tail>
struct add<i, tail...>
 { static constexpr int value = i + add<tail...>::value; };

Я имею в виду: не две специализации, а основная версия (это основание для рекурсии) и одна специализация (случай рекурсии).

Или, по крайней мере, я вижу это как небольшое упрощение.

1 голос
/ 24 мая 2019

Также возможно сделать это для нецелых типов с некоторой модификацией!

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

Поскольку плавающие типы не могут отображаться как шаблоны не типового типа, не шаблонные параметры / аргументы, лучший следующий вариант -возьмите их по ссылке.

Таким образом, struct будет выглядеть следующим образом:

template<auto& ...>
struct add{
    static constexpr auto value = 0;
};
template<auto& first, auto& ... others>
struct add<first, others...>{
    static constexpr auto value = first + add<others ...>::value;
};

Вначале значения должны храниться как константы (со связью), так что до main():

const auto v1 = 12; //int
const auto v2 = 54L; //long
const auto v3 = 3.25242; //double
const auto v4 = 75.7256L; //long double

Тогда их можно использовать в любом месте:

#include <iostream>
int main(){
    std::cout << add<v1, v2, v3, v4>::value << std::endl;
}

Возможный вывод:

144.978

Работает не только с (смешанными) целочисленными типами и (смешанными) плавающимитипы, но также любой пользовательский тип при условии, что пользовательские типы удовлетворяют определенным свойствам, включая наличие constexpr конструкторов и operator +.Он также должен иметь какой-либо оператор преобразования типа или другие средства для достижения аналогичной функциональности.Например, можно использовать этот тип:

class custom_type{
    const float v;
    //this one works too but the first is better for the purpose.
    //float v;
public:
    template<typename T>
    constexpr custom_type(T v_):v(v_){}
    template<typename T>
    constexpr auto operator +(T o)const{
        return o + 7345 + v ;
    }
    //this one works but the next one is  better for the purpose.
    //operator auto()const{
    //this one works too but the next one is more clear.
    //constexpr operator auto()const{
    template<typename T>
    constexpr operator T()const{
        return v;
    }
};

Соединение всего этого вместе:

template<auto& ...>
struct add{
    static constexpr auto value = 0;
};
template<auto& first, auto& ... others>
struct add<first, others...>{
    static constexpr auto value = first + add<others ...>::value;
};

class custom_type{
    const float v;
public:
    template<typename T>
    constexpr custom_type(T v_):v(v_){}
    template<typename T>
    constexpr auto operator +(T o)const{
        return o + 7345 + v ;
    }
    template<typename T>
    constexpr operator T()const{
        return v;
    }
};

const auto v1 = 12; //int
const auto v2 = 54L; //long
const auto v3 = 3.25242; //double
const auto v4 = 75.7256L; //long double
const custom_type v5 = 34.234; //custom_type

#include <iostream>
int main(){
    std::cout << add<v1, v2, v3, v4, v5>::value << std::endl;
}

Возможный вывод:

7524.21

Обратите внимание на struct add дляВерсии C ++ ниже 17 могут принимать аргументы только одного типа, а для типа double будет выглядеть так:

template<const double& ...>
struct add{
    static constexpr double value = 0;
};
template<const double& first, const double& ... others>
struct add<first, others...>{
    static constexpr double value = first + add<others ...>::value;
};

И константы:

const double v1 = 12;
const double v2 = 54L;
const double v3 = 3.25242;
const double v4 = 75.7256l;

Удачи!

1 голос
/ 23 мая 2019

Я предполагаю, что вы не хотите писать простое выражение сгиба по любой причине.

Нам нужно фактическое значение типа T как (нетип аргумента шаблона.Самый простой способ получить это - использовать auto в качестве типа:

template<auto ...>
struct add;

template<auto t>
struct add<t>
{
    static constexpr auto value = t;
};

template<auto t, auto... args>
struct add<t, args...>
{
    static constexpr auto value = t + add<args...>::value;
};

Демо:

#include <iostream>
#include <string>
int main()
{
    std::cout << add<1, 2, 3>::value << '\n';
    std::cout << add<1u, 2, -4>::value << '\n';
}
...