How to apply function to all elements in array (in C++ template class)

12,207

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);
Share:
12,207
Harry
Author by

Harry

Updated on June 07, 2022

Comments

  • Harry
    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
    Harry about 9 years
    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.
  • Harry
    Harry about 9 years
    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<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
    Mo Beigi about 9 years
    I don't understand, you changed function to std::cos and are using std::vector<std::complex<double>>? This works fine for me.
  • Mo Beigi
    Mo Beigi about 9 years
    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<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
    Mo Beigi about 9 years
    Alternatively, 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 the complex library. You would then have to convert the real and imag types back to the T typename. Ie, you have complex<int>(1,1), you make a temp complex<double>(1.0,1.0), call std::abs or std::cos on that and return complex<int>(static_cast<int>(temp.real()), static_cast<int>(temp.imag()));
  • Harry
    Harry about 9 years
    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?
  • PaulMcKenzie
    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 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.
  • Harry
    Harry about 9 years
    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::vectors by overloading every function individually, but not using one generic function...