#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. Из-за этого ему необходимо использовать внутренний тип класса.