如何强制C++从全局命名空间中选择一个函数

How do I force C++ to choose a function from the global namespace?

本文关键字:函数 一个 选择 C++ 何强制 全局 命名空间      更新时间:2023-10-16

我有一个容器,并希望依靠使用我的库的人来确保函数可用于底层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;
}

容器<>提供了一个 pow(( 方法,该方法应该依赖于全局命名空间中底层类型提供的 pow((。

这应该便于使用自定义的类似数字的类型。 即,库用户应该能够定义自己的类型,这些类型的行为类似于数字,并为他们的类型提供 pow(( 函数,使其<>容器兼容

问题是,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

这解决了实际问题,但我想知道为什么有必要?签名匹配不应该允许编译器选择正确的函数吗?

您需要

pow函数中引入std::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;
}

现场示例

这与构建自定义交换函数时要执行的操作相同。 您可以在此处看到它与具有自己pow的类一起工作


为那些不了解查找的人编辑。了解两者之间的区别很重要

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,则会失败(如果<complex>#include d(,则std::complex<double>(。前者将使用ADL来查找最佳匹配pow()函数,这可能是std::pow

此问题与模板无关。试试这段代码:

#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;
}

这将产生与您相同的错误。问题是(引用这个答案(:

在类范围内找到名为 Foo 的成员函数,然后名称查找将停止,因此永远不会考虑全局版本 Foo 进行重载解析,即使全局版本在这里更合适。这是一种名字隐藏。

或者从这个:

通常,当作用域嵌套时,在内部作用域中声明的任何名称都会

在外部作用域中隐藏具有相同名称的任何实体。因此,在这种情况下,在类作用域中使用时,在类中声明的名称将隐藏在封闭命名空间中声明的名称。

最终,另一个类似的行为是重写派生类中的函数会隐藏基类中的其他重载。

只需使用::

template <typename T>
struct container {
    T value;
    container<T> pow(T const exp) const {
       return {::pow(this->value, exp)};
    }
};

如果没有::则仅测试结构的pow是否匹配。如果没有,如果你犯了一个错误,你将在不注意的情况下使用全局函数。

简单的答案是:您不会强迫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)};
  }
};

通过 ADL 查找 T = foo::barfoo::pow()T = doublestd::pow() 查找。

int main()
{
  container<foo::bar> value{81.};
  std::cout << value.value << "^0.25 = "
            << value.pow(foo::bar{0.25}).value << 'n';
}

全局 pow 由 container::p ow 隐藏,名称隐藏规则位于 3.3.10 第 1 段 (*( 中。因此,当名称查找发生时,它不可见,因此找不到它,因此它不能参与重载解析。由于过载解析已经是C++中最复杂的部分之一,因此将外部作用域的名称交错在一起可能会导致太多意外;如果来自任意外部作用域的函数可能涉及特定函数调用的重载解析,则可能需要进行广泛搜索才能找出发生特定重载解析的原因。

常见的头文件可以将大量普通程序员不知道的东西带入全局范围。(该规则非常古老,早于将所有标准名称放在命名空间 std 中的决定::...但是我们仍然需要它,因为人们使用USING指令(不同于使用声明,只引入一个名称(将大量他们不一定知道的名称带入一个范围。

例如,标头<algorithm>使用多个名称,这些名称被许多程序员广泛用于完全不同的目的;考虑函数模板 std::count((,它有一个许多程序员可能用于循环索引的名称 - 或作为计数事物的函数。或者考虑函数模板 std::min(( 和 std::max(( 。许多较旧的代码库都有自己的最小值或最大值。(虽然这些通常是宏,但我不会进入一团蠕动的蠕虫。

相关文章: