4
votes

Take this example:

#include <vector>
#include <algorithm>
#include <iostream>
#include <cstdlib>
#include <cmath>

void PrintVec(const std::vector<float>&);
int main(int argc, char * argv[]){

float vals[] = {-1.2,0.0,1.2};
std::vector<float> test(vals, vals + sizeof(vals) / sizeof(float));
std::vector<float> absTest(3);

std::transform(test.begin(), test.end(), absTest.begin(), std::abs<float>());

PrintVec(test);
PrintVec(absTest);

return 0;
}

void PrintVec(const std::vector<float> &vec){
for (unsigned int i = 0; i < vec.size(); ++i){
    std::cout << vec[i] << '\n';
}
return;
}

Using both gcc 4.3.4 and VS 2013 I get compiler errors. For gcc its:

testTransformAbs.cpp:15: error: expected primary-expression before 'float'

For VS 2013 its:

error C2062: type 'float' unexpected

If I remove <float> then I get this error:

testTransformAbs.cpp:15: error: no matching function for call to 'abs()' /usr/include/stdlib.h:766: note: candidates are: int abs(int) /usr/include/c++/4.3/cstdlib:144: note: long int std::abs(long int) /usr/include/c++/4.3/cstdlib:175: note: long long int __gnu_cxx::abs(long long int) /usr/include/c++/4.3/cmath:99: note: double std::abs(double) /usr/include/c++/4.3/cmath:103: note: float std::abs(float) /usr/include/c++/4.3/cmath:107: note: long double std::abs(long double)

I can create my own function

float MyAbs(float f){
    return sqrt(f * f);
}

std::transform(test.begin(), test.end(), absTest.begin(), MyAbs);

And everything works. The reference on cplusplus.com says that the fourth input can be an UnaryOperation defined by:

Unary function that accepts one element of the type pointed to by InputIterator as argument, and returns some result value convertible to the type pointed to by OutputIterator. This can either be a function pointer or a function object.

To me this should be able to use std::abs(). I also tried fabs with the same result. What am I missing?

2
Why do you pass your own function only by name, but std::abs<float>()? – Slava
@Slava I tried it both ways. Just didn't include all the various ways I tried things. – Matt

2 Answers

6
votes

std::abs is an overloaded function, not a template function. When obtaining a pointer to the function, you can choose a specific overload by casting:

std::transform(test.begin(), test.end(), absTest.begin(),
    static_cast<float (*)(float)>(&std::abs));

or by using a function pointer variable:

float (*fabs)(float) = &std::abs;
std::transform(test.begin(), test.end(), absTest.begin(), fabs);

Note that I also removed the () you put after abs, since this is a function and not a class that needs to be instantiated.

4
votes

std::abs is not a template. Any function in headers prefixed with a c like cmath or cstdlib do not have any C++ features like templates, because they represent the C standard library. Also std::abs is for integral types. You should be using std::fabs for floating point types.

I dont like function pointer casts, so in cases like this one, i usually write some wrappers like these:

namespace hlp {
template <class T> struct iabs { 
    static_assert(std::is_integral<T>::value, "");
    T operator()(T const& t){ return std::abs(t); } 
};
template <class T> struct fabs { 
    static_assert(std::is_floating_point<T>::value, ""); 
    T operator()(T const& t){ return std::fabs(t); } 
};
}

You can use those wrappers like you wanted to use std::abs in your question. The static_assert will generate a clean compiler error when you try to use the integral version for floating point types or vice versa.