Why does the following `std::transform` example need a function pointer instead of a function object?

Viewed 395

The function template std::transform() takes a range, operates on it component-wise with an operator, and saves the result in another range. In the following example, the function takes a generic std::initializer_list called nl and operates on it with (std::string (*)(T)) std::to_string to transform all of its entries into strings and then stores the result in an array of strings called buffer.

class num_list
{
public:
    template<typename T>
    num_list(std::initializer_list<T> nl):
        size{nl.size()},
        buffer(new std::string[size])
    {
        std::transform(nl.begin(), nl.end(), // input sequence
        buffer,                              // output result
        (std::string (*)(T))std::to_string); // unary operator
    }
    //...
private:
    std::size_t size;
    std::string * buffer;
};

I am wondering why we need to typecast std::to_string into a function pointer for this to work. Why does the code fail to compile with C++11 if we drop the casting to pointer-to-function type (std::string (*)(T))? I can't decipher the complain thrown by the compiler.

error: no instance of overloaded function "std::transform" matches the argument list
1 Answers

std::transform is a function template that takes the function object via a template parameter type. Since the type is a template parameter it must be deduced. std::to_string is a overloaded function so when you try to deduce its type, you get multiple results. Since there is nothing else in the deduction to help narrow down the type, the compiler will not be able to deduce the type of std::to_string and you'll get an error.

When you cast std::to_string to a std::string (*)(T), you now only have single type for the compiler to deduce, which it does, and you can compile successfully.

Instead of casting, you can use a generic lambda to forward to std::to_string instead like

std::transform(nl.begin(), nl.end(), // input sequence
buffer,                              // output result
([](auto&& val){ return std::to_string(val);});

but this requires at least C++14. For C++11 you can use T for the parameter type like

std::transform(nl.begin(), nl.end(), // input sequence
buffer,                              // output result
([](const T& val){ return std::to_string(val);});
Related