6
votes

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.

3
How about simply std::for_each?PaulMcKenzie
As to begin "ugly", if you're using C++ 11, the std::transform can be written using a lambda without the typedef: ideone.com/stYywtPaulMcKenzie
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

3 Answers

4
votes

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);
0
votes

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

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