问题描述
我知道这是一个非常古老的话题,但我不明白为什么它在这里表现得如此。
程序:
#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
^
........
所以,如果
-
sqrt
编译器可以推断参数/返回类型并使用此函数。 -
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 所示。
所以,我的问题是三重的:
- 是否在某处声明了全局数学函数?我想当你在没有
std::
的情况下使用它时,它只是使用 ADL 或 smth 来找到它。另外,我不包括<math.h>
。 - 如果在某个库中定义了
sqrt
,但我想要标准版本,如何处理? - 为什么它不能推导出
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
。