简体   繁体   中英

Template function with template arguments or typename

I am creating a template class that contains a vector of numerical data (can be int, float, double, etc). And it has one operation, which calls std::abs() on the data. Something like the following code.

#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;
}

This works correctly for types such as int, float, double. I also want to be able to use this class for types such as std::complex<double> .

Looking around I found that I could use template template arguments:

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;
}

The previous code does not work as my template class expects two arguments,

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

Ideally I would like something like the previous code. In which the my_abs() function returns the type of the template argument passed to my template. Eg if I use a std::complex<double> then my main function could look something like:

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;
}

I am not sure how this can be achieved (or if it is even possible using the same template class).

You can use the return type of std::abs(T) in your declaration.


Example:

#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;
}

You have to write your specialization in this way

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;    
};

You don't need a class for this, a template function is sufficient. You can give an abs function to your template function, eg

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

    return out;
}

calling this as

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

and if you have different types for input and output, you can parameterize on T and U .


As @Jarod42 already noted, this doesn't work for overloaded functions like std::abs . You can work around this by giving a lambda as the second argument, eg

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

Another workaround is to explicitly select the appropriate abs by casting to the proper type

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM