How to apply function to all elements in array (in C++ template class)
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);
Harry
Updated on June 07, 2022Comments
-
Harry almost 2 years
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);
N.B. 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.
-
Harry about 9 yearsThanks, I will read through that. But
abs()
is not defined this way for complex numbers. For a complex number z, abs(z) = sqrt(z.real()*z.real() + z.imag()*z.imag()). In this case, the return type should be non-complex. The C++ implementation is described at link. -
Harry about 9 yearsSo, this still works if I change the function to std::cos. It also works if I keep the function as std::abs and change the input type to std::vector<double>. However, it does not work if I change both the function and the type. I get
error: cannot convert 'std::complex<double>' to 'double' in assignment
. -
Mo Beigi about 9 yearsI don't understand, you changed function to std::cos and are using std::vector<std::complex<double>>? This works fine for me.
-
Mo Beigi about 9 yearsIf you are talking about type of the complex objects then you must use either
float
,double
orlong double
as the behaviour is otherwise unspecified. If you want to add in that functionality you will have to make your own templates function (like I did above for abs) and implement the correct functionality (abs(z) = sqrt(z.real()*z.real() + z.imag()*z.imag())) and return a complex<T> object as the answer. In the case of abs you could store the answer in the real part of the complex object as that is how its implemented. -
Mo Beigi about 9 yearsAlternatively, you could create a templated function where you create a complex<T> by casting a
complex<float|double|double long>
and then call the functions specified in thecomplex
library. You would then have to convert the real and imag types back to the T typename. Ie, you havecomplex<int>(1,1)
, you make a tempcomplex<double>(1.0,1.0)
, callstd::abs
orstd::cos
on that and returncomplex<int>(static_cast<int>(temp.real()), static_cast<int>(temp.imag()));
-
Harry about 9 yearsI just upgraded my compiler yesterday so I can use C++11. That lambda syntax looks pretty weird, so it will take me a while to figure out what is going on... but it sort of looks like I have to write the template type of
v
manually. What happens if the type ofv
changes? Can I get the function to figure this out automatically? -
PaulMcKenzie about 9 years@Harry
That lambda syntax looks pretty weird
Get used to it. It is a major part of C++ 11. Second, change the type ofv
to what other types? Whatever it is, it has to satisfy the conditions that you've set -- you can't assume that you can write a function wherev
can be anything you want it to be. -
Harry about 9 yearsI think what I mean is that, if
v
were just a scalar, then (regardless of its type) I could pass it in tostd::cos
and the appropriate function would always be called automatically. I guess I might be able to achieve the same forstd::vector
s by overloading every function individually, but not using one generic function...