I have a template class that stores an array of numbers and I want to apply existing (scalar) functions to every element. For example, if we assume my class is std::vector, then I want to be able to call (for example) the std::cos function on all elements.
Maybe a call would look like this:
std::vector<float> A(3, 0.1f);
std::vector<float> B = vector_function(std::cos, A);
NB I must also handle std::complex<> types (for which the appropriate complex std::cos function is called).
I found this answer which suggests taking the function type as a template parameter:
template<typename T, typename F>
std::vector<T> vector_function(F func, std::vector<T> x)
However, I couldn't get this to work at all (maybe because functions like std::sin and std::cos are both templated and overloaded?).
I also tried using std::transform
, but this quickly became very ugly. For non-complex types, I managed to get it working using a typedef:
std::vector<float> A(2, -1.23f);
typedef float (*func_ptr)(float);
std::transform(A.begin(), A.end(), A.begin(), (func_ptr) std::abs);
However, attempting the same trick with std::complex<> types caused a run-time crash.
Is there a nice way to get this working? I have been stuck on this for ages.
I still think you should use std::transform
:
template <class OutputIter, class UnaryFunction>
void apply_pointwise(OutputIter first, OutputIter last, UnaryFunction f)
{
std::transform(first, last, first, f);
}
This function works not only for std::vector
types but indeed any container that has a begin()
and end()
member function, and it even works for C-style arrays with the help of the free functions std::begin
and std::end
. The unary function may be any free function, a functor object, a lambda expression or even member functions of a class.
As for the problem with std::sin
, this free function is templated and so the compiler cannot know which template instantiation you need.
If you have access to C++11, then simply use a lambda expression:
std::vector<float> v;
// ...
apply_pointwise(v.begin(), v.end(), [](const float f)
{
return std::sin(f);
});
This way, the compiler knows that it should substitute T=float
as the template parameter.
If you can use C functions, you can also use the function sinf
, which is not templated and takes a float
as a parameter:
apply_pointwise(v.begin(), v.end(), sinf);
You should have a look at this post by Richel Bilderbeek ( Math code snippet to make all elements in a container positive ) which shows you why abs won't work in transform like that. The reason it won't work is due the abs function structure (see http://www.cplusplus.com/reference/cstdlib/abs/ ). You will see abs
is not templated itself unlike some other functions (mostly binary functions) found in the functional
library. A solution available on Richel's website to show you how you would apply abs to say, a vector of integers.
Now if you wanted to apply abs to a container using transform, you should know that the transform function received a complex object and will not know how to apply abs to it. The easiest way to solve this is to simply write your own templated unary functions.
I have an example below where I apply abs to the real and imaginary parts of the complex object.
#include <iostream>
#include <vector>
#include <algorithm>
#include <complex>
#include <functional>
template <typename T>
std::complex<T> abs(const std::complex<T> &in) {
return std::complex<T>(std::abs(in.real()), std::abs(in.imag()));
};
int main() {
std::vector<std::complex<int>> v;
std::complex<int> c(10,-6);
v.push_back(c);
std::complex<int> d(-5, 5);
v.push_back(d);
std::transform(v.begin(), v.end(), v.begin(), abs<int>);
//Print out result
for (auto it = v.begin(); it != v.end(); ++it)
std::cout << *it << std::endl;
return 0;
}
As you have mentioned, you wanted to do apply the abs from the complex
library. To avoid the unspecified behaviour (see this ) you would use the double
typename for the complex objects.
#include <iostream>
#include <vector>
#include <algorithm>
#include <complex>
#include <functional>
int main() {
std::vector<std::complex<double>> v;
std::complex<double> c(3, 4);
v.push_back(c);
std::complex<double> d(-5, 5);
v.push_back(d);
std::transform(v.begin(), v.end(), v.begin(), std::abs<double>); //abs from <complex>
//Print out result
for (auto it = v.begin(); it != v.end(); ++it)
std::cout << *it << std::endl;
return 0;
}
If you can use valarray instead of vector, then you can do like this
#include <iostream>
#include <valarray>
int main()
{
valarray<double> dva{0.2, 0.4, 0.6, 0.8,1.0};
// scalar operation
dva += 0.25; // add 0.25 to each element of valarray inplace
// applying trignometric function
// apply sin function to each element and returns new valarray
valarray<double> dva2 = std::sin(0.7 * dva);
// apply custom function to each
// element. returns new valarray
valarray<double> dva3 = dva.apply([](double dval) {
return (dva * dva)/2;
});
return 0;
}
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.