带有和不带有 std:: 前缀的数学函数之间的另一个冲突

问题描述

我知道这是一个非常古老的话题,但我不明白为什么它在这里表现得如此。

程序:

#include <vector>
#include <algorithm>
#include <cmath>

int main()
{
    std::vector<double> a = {1,4,9,16};
    std::transform(a.begin(),a.end(),a.begin(),sqrt); // WORKS
    std::transform(a.begin(),std::sqrt); // compilation error
}

而编译错误是

g++ -std=c++17 -O3 main.cpp -Wall -Wextra -pedantic
main.cpp: In function ‘int main()’:
main.cpp:10:57: error: no matching function for call to ‘transform(std::vector<double>::iterator,std::vector<double>::iterator,<unresolved overloaded function type>)’
  std::transform(a.begin(),std::sqrt); // compilation error
                                                         ^
........

所以,如果

  1. sqrt 编译器可以推断参数/返回类型并使用此函数。
  2. std::sqrt 编译器无法在 std::sqrt 的不同重载之间进行选择吗?...

为什么会这样? 编译时所有类型都是已知的,它到底不能推断出什么? 为什么只是简单的 sqrt 工作(我找不到定义)?


以下内容可能表明正在发生某些事情:

#include <cmath>

int main()
{
    typeid(sqrt); // compiles
    typeid(std::sqrt); // compilation error
}

当它说没有上下文就无法理解 std::sqrt 的类型时,这是有道理的。

所以,从这个片段中可以看出 sqrt 被声明为 smth like

//global namespace
double sqrt(double);

std::sqrt 有许多重载,如 https://en.cppreference.com/w/cpp/numeric/math/sqrt 所示。

所以,我的问题是三重的:

  1. 是否在某处声明了全局数学函数?我想当你在没有 std:: 的情况下使用它时,它只是使用 ADL 或 smth 来找到它。另外,我不包括 <math.h>
  2. 如果在某个库中定义了sqrt,但我想要标准版本,如何处理?
  3. 为什么它不能推导出 std::sqrt 的参数/返回类型,如果它是已知的?

解决方法

错误信息的关键部分是“”。有多个版本的 std::sqrt 采用不同的参数类型,编译器无法知道在该上下文中您需要哪一个。

显然,在全局命名空间中,您的编译器提供了 C 名称:

float sqrtf(float);
double sqrt(double);
long double sqrtl(long double);

只有一个名为 sqrt 的函数,所以编译器知道使用哪个。

std 命名空间中还有更多:

float sqrt(float);             // 1
float sqrtf(float);
double sqrt(double);           // 2
long double sqrt(long double); // 3
long double sqrtl(long double);
double sqrt(Integral type);    // 4

所以有四个名为 std::sqrt 的函数,编译器没有任何规则可以选择一个。

要选择重载,您可以使用强制转换(是的,这不直观,而且坦率地说,很奇怪):

std::transform(a.begin(),a.end(),a.begin(),(double (*)(double))std::sqrt)

请注意,标记为“WORKS”的版本仅在偶然情况下才有效。 C++ 中不要求全局命名空间只有 C 名称。

,

是否在某处声明了全局数学函数?我想当你在没有 std:: 的情况下使用它时,它只是使用 ADL 或 smth 来找到它。另外,我不包括 <math.h>

<cmath> 可能包含也可能不包含 <math.h> 并因此公开 C sqrt 函数。这就是您的实施正在做的事情。

如果在某个库中定义了sqrt,但我想要标准版本,如何处理?

如果您想要 std::sqrt,请使用 std::sqrt。如果这样做,将不会使用其他功能。

为什么它不能推导出 std::sqrt 的参数/返回类型,如果它是已知的?

sqrt 的 C 版本不同,std::sqrt 被重载了

float       sqrt ( float arg );
double      sqrt ( double arg );
long double sqrt ( long double arg );
double      sqrt ( IntegralType arg );

并且当您将 std::sqrt 传递给 std::transform 时,编译器没有使用哪个重载的规则。为了解决这个问题,我们可以使用 lambda 将调用 sqrt 移动到编译器可以执行重载解析的上下文中。那看起来像

std::transform(a.begin(),[](auto val){ return std::sqrt(val); });

现在为您调用了正确版本的 std::sqrt

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...