Шаблонная функция с аргументами шаблона или именем типа

#c #templates #c 11 #c 14

#c #шаблоны #c 11 #c 14

Вопрос:

Я создаю класс шаблона, который содержит вектор числовых данных (может быть int, float, double и т. Д.). И у него есть одна операция, которая вызывает std::abs() данные. Что-то вроде следующего кода.

 #include <iostream>
#include <complex>
#include <vector>


template<typename T> class MyData
{
public:
   std::vector<T> data;
   MyData<T> my_abs() const;

};


template<typename T>
MyData<T> MyData<T>::my_abs() const
{
    MyData<T> output;
    output.data.reserve(data.size());
    typename std::vector<T>::const_iterator it;

    for (it = data.begin(); it != data.end(); it  )
    {
        output.data.push_back(std::abs(*it));
    }
    return output;
}


int main()
{
    MyData<double> A;
    A.data = std::vector<double>(10, -1.0);

    MyData<double> test = A.my_abs();

    for (auto el : test.data)
    {
        std::cout << el << std::endl;
    }
    return 0;
}
  

Это корректно работает для таких типов, как int, float, double . Я также хочу иметь возможность использовать этот класс для таких типов, как std::complex<double> .

Оглядевшись, я обнаружил, что могу использовать аргументы шаблона шаблона:

 template<template<typename> class T, typename U> class MyData
{
public:
   std::vector<T<U>> data;
   MyData<U> my_abs() const;

};


template<template<typename> class T, typename U>
MyData<U> MyData<T<U>>::my_abs() const
{
    MyData<U> output;
    output.data.reserve(data.size());
    typename std::vector<T<U>>::const_iterator it;

    for (it = data.begin(); it != data.end(); it  )
    {
        output.data.push_back(std::abs(*it));
    }
    return output;
}
  

Предыдущий код не работает, так как мой шаблонный класс ожидает два аргумента,

 error: wrong number of template arguments (1, should be 2)
MyData<U> abs() const;
       ^
  

В идеале я хотел бы что-то вроде предыдущего кода. В котором my_abs() функция возвращает тип аргумента шаблона, переданного моему шаблону. Например, если я использую a std::complex<double> , то моя основная функция может выглядеть примерно так:

 int main()
{
    MyData<std::complex<double>> A;
    A.data = std::vector<std::complex<double>>(10, std::complex<double>(-1.0, -1.0));

    MyData<double> test = A.my_abs();

    for (auto el : test.data)
    {
        std::cout << el << std::endl;
    }
    return 0;
}
  

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

Комментарии:

1. Просто специализируйте свою 1-ю версию для типа std::complex<double> maybe .

Ответ №1:

Вы можете использовать возвращаемый тип std::abs(T) в своем объявлении.


Пример:

 #include <iostream>
#include <complex>
#include <vector>
#include <cmath>
#include <utility>

template<typename T> class MyData
{
public:
   std::vector<T> data;
   using abs_type = decltype(std::abs(std::declval<T>()));
   auto my_abs() -> MyData<abs_type> const;
};

template<typename T>
auto MyData<T>::my_abs() -> MyData<abs_type> const
{
    MyData<abs_type> output;
    output.data.reserve(data.size());
    typename std::vector<T>::const_iterator it;

    for (it = data.begin(); it != data.end(); it  )
    {
        output.data.push_back(std::abs(*it));
    }
    return output;
}

int main()
{
    MyData<std::complex<double>> A;
    A.data = std::vector<std::complex<double>>(10, std::complex<double>(-1.0, -1.0));

    auto test = A.my_abs();

    for (auto el : test.data)
    {
        std::cout << el << std::endl;
    }
    return 0;
}
  

Комментарии:

1. Спасибо, это именно то, что я искал.

Ответ №2:

Вы должны написать свою специализацию таким образом

 template<template<typename> class T, typename U>
class MyData<T<U>> // <----- note the <T<U>>
{
public:
   std::vector<T<U>> data;
   MyData<U> my_abs() const;    
};
  

Комментарии:

1. Почему требуется специализация? Его оригинальный шаблон std::complex<double> также работает для.

2. @user2296177 — поскольку оператору необходимо извлечь форму U типа ( double части) T<U> для построения MyData<U> возвращаемого типа для my_abs()

3. @downvoter — пожалуйста, не могли бы вы объяснить, что не так в моем ответе?

4. Я не сторонник снижения, и я не вижу ничего плохого в этом подходе, кроме того, что он может быть более подробным, чем другие.

Ответ №3:

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

 template<typename T, typename F> std::vector<T> my_abs(const std::vector<T> amp;in, F abs) {
    std::vector<T> out;
    for (auto amp;i: in) {
        out.push_back(abs(i));
    }

    return out;
}
  

вызывая это как

 std::vector<int> res = my_abs(in, special_abs);
  

и если у вас разные типы для ввода и вывода, вы можете параметризовать T и U .


Как уже отмечал @Jarod42, это не работает для перегруженных функций, таких как std::abs . Вы можете обойти это, указав лямбда-выражение в качестве второго аргумента, например

 std::vector<int> res = my_abs(in, [](const autoamp; e) { return std::abs(e);});
  

Другим обходным путем является явный выбор подходящего abs путем приведения к нужному типу

 std::vector<int> res = my_abs(in, static_cast<double(*)(double)>(std::abs));
  

Комментарии:

1. Поскольку std::abs имеет перегрузку, my_abs(in, std::abs); не будет компилироваться.

2. Вы могли бы сделать my_abs(in, [](const autoamp; e) { return std::abs(e);});

3. Для моего приложения класс необходим, поскольку функция abs является лишь одной из нескольких операций, которые можно выполнить с MyData. Из-за этого ему необходимо использовать внутренний тип класса.