8

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.

Community
  • 1
  • 1
Harry
  • 884
  • 1
  • 8
  • 19
  • 1
    How about simply `std::for_each`? – PaulMcKenzie Apr 06 '15 at 09:52
  • 1
    As to begin "ugly", if you're using C++ 11, the `std::transform` can be written using a lambda without the `typedef`: http://ideone.com/stYywt – PaulMcKenzie Apr 06 '15 at 10:05
  • I looked up `std::for_each` and it seems to just call a function on each value... I can't see how to keep the result (maybe `std::transform` is preferable in this respect?). I haven't used lambda functions before. Is there a way to make this generic/templated? – Harry Apr 06 '15 at 10:12

3 Answers3

6

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);
rwols
  • 2,968
  • 2
  • 19
  • 26
  • I 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 of `v` changes? Can I get the function to figure this out automatically? – Harry Apr 06 '15 at 13:44
  • @Harry `That lambda syntax looks pretty weird` Get used to it. It is a major part of C++ 11. Second, change the type of `v` 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 where `v` can be anything you want it to be. – PaulMcKenzie Apr 06 '15 at 14:36
  • I think what I mean is that, if `v` were just a scalar, then (regardless of its type) I could pass it in to `std::cos` and the appropriate function would always be called automatically. I guess I might be able to achieve the same for `std::vector`s by overloading every function individually, but not using one generic function... – Harry Apr 06 '15 at 22:02
0

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;
}
Community
  • 1
  • 1
Mo Beigi
  • 1,614
  • 4
  • 29
  • 50
  • Thanks, 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](http://www.cplusplus.com/reference/complex/abs/). – Harry Apr 06 '15 at 10:38
  • So, 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. However, it does not work if I change both the function and the type. I get `error: cannot convert 'std::complex' to 'double' in assignment`. – Harry Apr 06 '15 at 11:23
  • I don't understand, you changed function to std::cos and are using std::vector>? This works fine for me. – Mo Beigi Apr 06 '15 at 11:25
  • If you are talking about type of the complex objects then you must use either `float`, `double` or `long 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 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 Apr 06 '15 at 11:34
  • Alternatively, you could create a templated function where you create a complex by casting a `complex` and then call the functions specified in the `complex` library. You would then have to convert the real and imag types back to the T typename. Ie, you have `complex(1,1)`, you make a temp `complex(1.0,1.0)`, call `std::abs` or `std::cos` on that and return `complex(static_cast(temp.real()), static_cast(temp.imag()));` – Mo Beigi Apr 06 '15 at 11:36
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;
}
NeutronStar
  • 220
  • 1
  • 4
  • 17