How to use std::sqrt as std::function?

Viewed 101

Here's the code:

#include <iostream>
#include <cmath>
#include <functional>
#include <complex>

int main() {
    // This works.
    std::function<float(float)> f = [](auto const& x) {return std::sqrt(x);};   
    
    // This also works. Why this works?!
    using Complex = std::complex<double>;
    std::function<Complex(const Complex&)> g = std::sqrt<double>;
    
    // All of the following doesn't work.
    // error: conversion from ‘<unresolved overloaded function type>’
    // to non-scalar type ‘std::function<float(float)>’ requested
    std::function<float(float)> a = std::sqrtf<float>;
    std::function<float(float)> b = std::sqrt<float>;
    std::function<float(float)> c = std::sqrt;
    std::function<double(double)> d = std::sqrt<double>;
}

Considering the std::sqrt reference, I am really confused as to why the one involving complex works, and why the other ones do not work.

I am aware of this question, but, I am not interested in using std::complex, and, the OP from this question specifically asks for std::complex, and, in contrast, I'd like to work only with float or double (or real valued, not complex).

What's going on? What's the correct way of doing this?

2 Answers

The floating-point overloads of std::sqrt() and std::sqrtf() are not templates, so these forms are invalid syntax:

std::function<float(float)> a = std::sqrtf<float>;
// and
std::function<double(double)> d = std::sqrt<double>;

This form:

std::function<float(float)> c = std::sqrt;

Would be okay, except that std::sqrt() is overloaded, so the name can't decay to a single pointer like it would if it wasn't overloaded.

In order to remove the ambiguity of which overload to use, you need to cast the function to the correct type:

std::function<float(float)> works = static_cast<float(*)(float)>(std::sqrt);

But as you can see, the syntax is a little verbose, which is why the lambda version is the go-to way to do this.

The reason this form works:

std::function<Complex(const Complex&)> g = std::sqrt<double>;

Is because the std::complex version of std::sqrt() is a template, where the template parameter is the underlying type of the complex object.

Considering the std::sqrt reference,

You're looking the wrong std::sqrt page: it's the page of the non-template version.

If you use std::sqrt<double> and std::sqrt<float> functions, you're using the template version of std::sqtr, that is referenced in this page.

As you can see, std::sqrt<T>

template< class T >
complex<T> sqrt( const complex<T>& z );

receive a std::complex<T> and return a std::complex<T>.

So when you write

std::function<float(float)> f = [](auto const& x) {return std::sqrt(x);}; 

works because the lambda call (std::sqrt(x), where x is a float) the not template function.

When you write

std::function<Complex(const Complex&)> g = std::sqrt<double>;

works because std::sqrt<double> is the template version of std::sqrt that receive a Complex const & (std::complex<double> const &) and return a Complex const &

But when you write something as

std::function<float(float)> b = std::sqrt<float>;
std::function<double(double)> d = std::sqrt<double>;

you pass function receiving and returning a complex to std::function waiting for function receiving and returning a simple (not complex) floating point type.

To make it works, you have to use the non-template version of std::sqrt (so no <float> and no <double>) and cast the right pointer type (to select the right version of the std::sqrt non-template but overloaded version). This works also for c.

std::function<float(float)> b = (float(*)(float))std::sqrt;
std::function<float(float)> c = (float(*)(float))std::sqrt;
std::function<double(double)> d = (double(*)(double))std::sqrt;

For a the problem

std::function<float(float)> a = std::sqrtf<float>;

is different; you have to remove the template part (<float>), given that std::sqrtf isn't a template function.

So should works (std::sqrtf isn't overloaded, so no cast should be required, given there isn't ambiguity)

std::function<float(float)> a = std::sqrtf;

Unfortunately I see that this doesn't works with clang++ and with g++. As far I understand it's because cmath doesn't put sqrtf inside the std namespace (and seems to me that g++ and clang++ are not conforming).

So (with g++ and clang++) works

std::function<float(float)> a = sqrtf;
Related