I am coming somewhat belatedly to Functional Programming, and getting my head around ranges/views. I'm using MSVC19 and compiling for C 20.
I'm using std::views::transform and the compiler doesn't seem to be inferring type as I might naively hope.
Here's a small example, which simply takes a vector of strings and computes their length:
#include <vector>
#include <iostream>
#include <ranges>
template<typename E>
auto length(const E& s)
{
std::cout << "Templated length()\n";
return static_cast<int>(s.length());
}
template<typename E>
auto getLengths(const std::vector<E>& v)
{
return v | std::views::transform(length<E>);
}
int main()
{
std::vector<std::string> vec = { "Larry","Curly","Moe" };
for (int i : getLengths(vec))
{
std::cout << i << "\n";
}
return 0;
}
with the output:
Templated length()
5
Templated length()
5
Templated length()
3
My question is why does changing the code in this line (dropping the <E>):
return v | std::views::transform(length);
give me an armful of errors, starting with: Error C2672 'operator __surrogate_func': no matching overloaded function found ?
Why doesn't the compiler infer that the type is std::string? If I replace the templates with a non-templated function:
auto length(const std::string& s) -> int
{
std::cout << "Specialized length()\n";
return static_cast<int>(s.length());
}
The code compiles and runs, so clearly without the template, the compiler finds a match for the particular type I am using.
CodePudding user response:
This has nothing to do with views. You can reduce the problem to:
template <typename T>
int length(T const& x) { return x.length(); }
template <typename F>
void do_something(F&& f) {
// in theory use f to call something
}
void stuff() {
do_something(length); // error
}
C doesn't really do type inference. When you have do_something(length), we need to pick which length we're talking about right there. And we can't do that, so it's an error. There's no way for do_something to say "I want the instantiation of the function template that will be called with a std::string - it's entirely up to the caller to give do_something the right thing.
The same is true in the original example. length<E> is a concrete function. length is not something that you can just pass in.
The typical approach is to delay instantiation by wrapping your function template in a lambda:
void stuff() {
do_something([](auto const& e) { return length(e); }); // ok
}
Now, this works - because a lambda is an expression that has a type that can be deduced by do_something, while just length is not. And we don't have to manually provide the template parameter, which is error prone.
We can generalize this with a macro:
#define FWD(arg) static_cast<decltype(arg)&&>(arg)
#define LIFT(name) [&](auto&&... args) -> decltype(name(FWD(args)...)) { return name(FWD(args)...); }
void stuff() {
do_something(LIFT(length));
}
Which avoids some extra typing and probably makes the intent a little clearer.
