我有一个容器,并希望依赖于使用我的库的人来确保在下一个示例中有一个函数可用于底层的 value_type ( pow() )。我希望编译器根据其签名在具有相同名称的成员函数中使用该函数。
我尝试创建一个最小的例子:
#include <iostream>
#include <cmath>
using std::pow;
template <typename T>
struct container {
T value;
container<T> pow(T const exp) const {
return {pow(this->value, exp)};
}
};
int main() {
container<double> value{81.};
std::cout << value.value << "^0.25 = " << value.pow(.25).value << '\n';
return 0;
}
容器&lt;&gt; 提供 pow()方法,该方法应该依赖于 pow()可从底层获取输入全局命名空间。
这应该有助于使用自定义数字类型。即库用户应该能够定义自己的类型,这些类型与数字类似,并为其类型提供 pow()函数,使其与容器&lt;&gt; 兼容。
问题是,clang和gcc都没有从全局命名空间中获取函数:
c++ -std=c++11 pow.cpp -o pow
pow.cpp:11:28: error: too many arguments to function call, expected single argument 'exp', have 2 arguments
return {pow(this->value, exp)};
~~~ ^~~
pow.cpp:17:50: note: in instantiation of member function 'container<double>::pow' requested here
std::cout << value.value << "^0.25 = " << value.pow(.25).value << '\n';
^
pow.cpp:10:2: note: 'pow' declared here
container<T> pow(T const exp) const {
^
如果我显式使用全局命名空间,它会按预期工作:
container<T> pow(T const exp) const {
return {::pow(this->value, exp)};
}
程序产生预期的输出:
c++ -std=c++11 pow.cpp -o pow
./pow
81^0.25 = 3
这解决了实际问题,但我想知道为什么有必要?签名匹配是否不允许编译器选择正确的函数?
答案 0 :(得分:5)
您需要在std::pow
函数中引入pow
函数。如果ADL失败,这允许编译器回退到std::pow
#include <iostream>
#include <cmath>
template <typename T>
struct container {
T value;
container<T> pow(T const exp) const {
using std::pow;
return {pow(this->value, exp)};
}
};
int main() {
container<double> value{81.};
std::cout << value.value << "^0.25 = " << value.pow(.25).value << '\n';
return 0;
}
这与building a custom swap function时的情况相同。您可以看到它与具有自己的pow
here
编辑。了解
之间的区别非常重要T func(T a, T b)
{
using std::pow;
return pow(a,b);
}
和
T func(T a, T b)
{
return std::pow(a,b);
}
后者总是调用std::pow()
,如果T
无法转换为double
,则会失败;如果std::complex<double>
为<complex>
,则会失败#include
)。前者将使用ADL来查找最匹配的pow()
函数,该函数可能为std::pow
。
答案 1 :(得分:2)
此问题与模板无关。试试这段代码:
#include <iostream>
#include <cmath>
using std::pow;
struct container_double {
double value;
container_double pow(double const exp) const {
return {pow(this->value, exp)};
}
};
int main() {
container_double value{81.};
std::cout << value.value << "^0.25 = " << value.pow(.25).value << '\n';
return 0;
}
这将产生与您相同的错误。问题是(引自this回答):
在类范围内找到名为foo的成员函数,然后名称查找将停止,因此全局版本foo永远不会被视为重载解析,即使全局版本更合适这里。这是一种隐藏的名字。
或来自this一个:
通常,当嵌套作用域时,在内部作用域中使用该名称时,内部作用域中声明的任何名称都会隐藏外部作用域中具有相同名称的任何实体。因此,在这种情况下,当在类范围中使用时,在类中声明的名称将隐藏在封闭命名空间中声明的名称。
最终,另一个类似的行为是覆盖基类中派生类hides others overloads中的函数。
答案 2 :(得分:0)
只需使用::
template <typename T>
struct container {
T value;
container<T> pow(T const exp) const {
return {::pow(this->value, exp)};
}
};
如果没有::
,则仅测试结构的pow
是否匹配。如果没有,如果你犯了一个错误,你将使用全局函数而不会注意到它。
答案 3 :(得分:0)
全局pow由container :: pow隐藏,名称隐藏规则在3.3.10第1段(*)中。因此,当名称查找发生时它不可见,因此,它找不到,因此它无法参与重载解析。由于重载解析是C ++中最复杂的部分之一,因此将外部作用域中的名称与其交错可能会导致太多意外;如果来自任意外部作用域的函数可能涉及特定函数调用的重载解析,则可能必须进行远程搜索以找出发生特定重载解析的原因。
通用头文件可以将普通程序员不了解的大量内容带入全局范围。 (该规则很老,并且决定将所有标准名称放在命名空间std :: ...但我们仍然需要它,因为人们使用using指令(不同于使用声明,只引入一个名称)大量的名字,他们不一定知道范围。)
例如,标题<algorithm>
使用了许多程序员广泛使用的名称,用于完全不同的目的;考虑函数模板std :: count(),它具有许多程序员可能用于循环索引的名称 - 或者作为计算事物的函数。或者考虑函数模板std :: min()和std :: max()。许多较旧的代码库都有自己的最小值或自己的最大值。 (虽然那些通常都是宏,但是我赢了一个蠕动的蠕虫球。)
答案 4 :(得分:0)
简单的答案是:您不 强制C ++从全局命名空间中选择一个函数 。(句号)
相反,您在与类型相同的namespace
中声明与用户定义类型相关联的任何功能,即
namespace foo {
struct bar // the 'value_type'
{
double x;
};
// define functions related to type bar in same namespace
std::ostream& operator<<(std::ostream&s, bar x)
{
return s<<x.x;
}
foo::bar pow(foo::bar x, foo::bar y)
{
return {std::pow(x.x,y.x)};
}
}
当
template <typename T>
struct container {
T value;
container<T> pow(T const&exp) const
{
using std::pow;
return {pow(this->value, exp)};
}
};
通过foo::pow()
T
= foo::bar
找到std::pow()
,T
= double
找到int main()
{
container<foo::bar> value{81.};
std::cout << value.value << "^0.25 = "
<< value.pow(foo::bar{0.25}).value << '\n';
}
。
{{1}}