4

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
Nanashi No Gombe
  • 510
  • 1
  • 6
  • 19
  • 7
    Note that you aren't allowed to take the address of a function in the standard library in C++20 and upwards: https://en.cppreference.com/w/cpp/language/extending_std#Addressable_functions . This code would have unspecified behavior or could fail to compile. Instead, you should write `[](int i) { return std::to_string(i); };`, which sucks, but there are proposals for making it nicer. – Justin Jun 17 '19 at 16:23
  • 2
    `std::to_string` has many overloads, so `std::to_string` alone does not uniquely identify a function. See https://stackoverflow.com/questions/2942426/how-do-i-specify-a-pointer-to-an-overloaded-function. – Holt Jun 17 '19 at 16:27
  • Because the functor can be *any* object with an `operator()`, and there's no way for it to know which template instantiation or overload it should use. – o11c Jun 17 '19 at 16:27
  • The function `to_string` has overloads, by casting it to a function pointer you tell the compiler which one to choose. (see https://godbolt.org/z/nQrzWc, comment out the overload of goo::foo) –  Jun 17 '19 at 16:28

1 Answers1

8

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);});
NathanOliver
  • 171,901
  • 28
  • 288
  • 402