为什么clang找不到在调用站点之前声明的函数

Why clang does not find a function declared prior to the call site?

本文关键字:声明 函数 站点 clang 找不到 调用 为什么      更新时间:2023-10-16

我想为simd类型提供一些sqrt包装函数,这样它们就可以从模板中与std::sqrt一起使用。

现在我的问题是,它们不知何故是看不见的。只能使用std中定义的。

这是代码的高度简化部分:

#include <cmath>
#include <pmmintrin.h>
using float_simd = __m128;
using double_simd = __m128d;
float_simd sqrt(float_simd a) { return _mm_sqrt_ps(a); }
double_simd sqrt(double_simd a) { return _mm_sqrt_pd(a); }
template <typename T>
void do_fancy(T val) 
{
using std::sqrt;
auto ret = sqrt(val);
(void)ret;
}
int main() {
double testDouble = 1.0; 
float testFloat = 1.0f;
double_simd testSimdDouble;
float_simd testSimdFloat;
do_fancy(testDouble);
do_fancy(testFloat);
do_fancy(testSimdDouble);
do_fancy(testSimdFloat);
return 0;
}

现在clang给出了这个:

main.cpp:14:16: error: call to function 'sqrt' that is neither visible in the template definition nor found by argument-dependent lookup
auto ret = sqrt(val);
^
main.cpp:25:5: note: in instantiation of function template specialization 'do_fancy<__attribute__((__vector_size__(2 * sizeof(double)))) double>' requested here
do_fancy(testSimdDouble);
^
main.cpp:8:13: note: 'sqrt' should be declared prior to the call site
double_simd sqrt(double_simd a) { return _mm_sqrt_pd(a); }
^
main.cpp:14:16: error: call to function 'sqrt' that is neither visible in the template definition nor found by argument-dependent lookup
auto ret = sqrt(val);
^
main.cpp:26:5: note: in instantiation of function template specialization 'do_fancy<__attribute__((__vector_size__(4 * sizeof(float)))) float>' requested here
do_fancy(testSimdFloat);
^
main.cpp:7:12: note: 'sqrt' should be declared prior to the call site
float_simd sqrt(float_simd a) { return _mm_sqrt_ps(a); }
^

它说,simd-sqrt函数"应该在调用站点之前声明",但我认为它们是。

我在网上搜索了一下,但不幸的是,我只发现了一些案例,其中呼叫和声明的顺序实际上是错误的。

我刚刚评论了using std::sqrt,一切都很好。我不明白……它现在怎么能找到std::sqrt的?

我在macOS上使用自制软件的clang 3.9.1。

using std::sqrt;在函数体中添加了一个sqrt的声明,因此函数的名称查找会找到该声明,而不会在封闭范围中考虑函数体之外的声明。

这是"名称隐藏"的一种形式,它是C++的一种属性,其中一个作用域中的名称"隐藏"外部作用域中具有相同名称的实体。之所以会发生这种情况,是因为编译器从最内部的作用域开始查找名称,然后只有在没有匹配项的情况下才会尝试封闭作用域,直到到达最外部(即全局)作用域。因此,一旦在给定范围内找到一个或匹配的名称,它就会停止搜索该名称,并且不会看到外部范围内的匹配名称。

在代码中,名称sqrt是通过引用std::sqrt函数的using声明在函数体中声明的。名称查找从函数体的作用域开始,查找匹配项,而不在周围的命名空间中查找。

你需要做:

using std::sqrt;
using ::sqrt;

这意味着两组重载(您在全局命名空间中定义的重载和在命名空间std中定义的<cmath>标头)都在函数范围中声明,并且都可以通过名称查找找到。现在编译器将在函数范围中找到所有这些重载,重载解析将根据参数类型选择最佳重载。

另一种选择是使用声明将using std::sqrt移动到全局命名空间,而不是在函数体中。这将把std::sqrt添加到全局命名空间中,这样就不会隐藏您自己的sqrt重载。如果函数体中没有using声明,那么在最内部的作用域中就没有匹配项,因此编译器将在封闭作用域中查找,即全局命名空间。在这个范围内,它会找到所有重载,并且重载解决方案会选择最好的一个。

另一种选择是用<math.h>替换<cmath>并移除using std::sqrt;。这将确保sqrt的标准库版本在全局命名空间中声明,而不是在命名空间std中声明,因此您不需要using std::sqrt;能够将其称为不合格。

正如您在下面的注释中所指出的,如果您只是注释掉using声明,它可能也会起作用,但是这是不可移植的,并且不能保证起作用。它恰好与您使用的编译器一起工作,因为<cmath>同时声明了std::sqrt::sqrt(因此它相当于在全局命名空间中具有using std::sqrt;),但并非所有编译器都这样做。为了保证在全局命名空间中获得::sqrt,应该将using std::sqrt;放在全局命名空间或包含<math.h>