Do C++20 concepts enable fixing template function as template argument problem?

If you try something relatively simple in C++20 it implodes with unhelpful error message spam.

int main() {
    auto as = std::vector{1,3,24,};
    auto bs = std::vector{1,4,10};
    auto cs = std::vector<float>{};
    std::ranges::transform(as, bs, std::back_inserter(cs), std::max);
    std::ranges::copy(cs, std::ostream_iterator<float>(std::cout, " "));
}

Reason is that std::max is templated function, so it just does not work. This can be easily worked around by making argument a lambda or making a small helper functor.

But I was wondering if C++ concepts could be used to tell what template instantiation what we want? For example, we hack some requires statement that says that if functor argument is template then the template arguments of that functor must match the container value_type.

I doubt that is possible, since I think that exact type of functor must be known before template overload resolution and constraint cheks kick in, in other words, there is no “backpropagation” of information from concepts to callsite.

But I am not sure so I decided to ask.

If you want to know what I have tried, here is my code, but it is very broken, I am incapable of writing template template arguments code…

But anyways I hope it illustrates the idea…

template<typename C, typename F>
struct xtransform
{
    xtransform(C& c, F f) : c_(c), f_(f){}
    void operator()(){
    }
    C c_;
    F f_;
};
template<typename C, template<typename> typename F, typename FArg>
requires requires {
    std::is_same_v<typename C::value_type, FArg>;
}
struct xtransform<C, F<FArg>>
{
    xtransform(C& c, F<FArg> f) : c_(c), f_(f){}
    void operator()(C& c, F<FArg> f){
    }
    C c_;
    F<FArg> f_;
};

Answer

No.

std::max names a template function. Template function names with incomplete template parameter lists are turned into functions during overload resolution. Overload resolution requires converting the function name to a pointer to a fixed signature, or invoking with ()s.

Concepts don’t help.

Now you can use an older trick; have an overload of your function that takes a function pointer in a non-deduced context. When I write hand-rolled std function-like types, I add in a R(*)(Args...) overload for exactly this reason; that permits overload resolution when there is an overload whose types match exactly.

But that isn’t concept based.