【问题标题】:Is it a good practice to overload math functions in namespace std in c++在 c++ 中的命名空间 std 中重载数学函数是一个好习惯吗
【发布时间】:2014-03-27 23:31:26
【问题描述】:

我正在编写一个表示算术类型的 C++ 类(mpfr 周围的 c++ 包装器),并且我想支持 中的一些函数(我将以 std::sqrt 为例)。

所以我有以下课程:

namespace ns
{
  class MyClass
  {
      /* ... */
      public:
      friend MyClass sqrt(const MyClass& mc);
  };
}

我可以这样使用它:

MyClass c;
/* ... */
MyClass d = ns::sqrt(c);
MyClass e = sqrt(c); // Apparently I don't have to specify ns::

但我不能这样使用它:

MyClass f = std::sqrt(c);

编译器 (g++ (Debian 4.7.2-5)) 错误是:“没有匹配函数调用 sqrt(ns::MyClass&)”。

这很正常,但对我来说是个问题。我需要这是有效的,因为 MyClass 应该用于现有的模板函数(我不应该修改)。例如:

template <typename T>
void func(T a)
{
    /* ... */
    T c = std::sqrt(a);
    /* ... */
}
int main()
{
    func<float>(3);
    func<MyClass>(MyClass(3));
    /* ... */
}

以下代码实际上解决了我的问题:

namespace std
{
  using ns::sqrt;
}

但在 std 命名空间中添加东西对我来说似乎很不自然。我怕以后会遇到意想不到的麻烦,这样做。

安全吗?如果不是,为什么?

还有更好的选择吗?

【问题讨论】:

  • 我认为不经过测试是不安全的。说了这么多,我想如果你的实现看起来更好,为什么不把它提出标准呢?他们甚至会测试一些您可能没有想到的数据集。
  • @Claudiordgz 大概是用于多精度整数,所以它的用途与用于原生类型的std::sqrt 的用途正交。
  • @user1520427 明白了,谢谢

标签: c++ std overloading cmath


【解决方案1】:

标准禁止向命名空间std 添加东西。解决这个问题的正确方法通常是使用swap(在std 命名空间中可用,但可以通过用户定义的类型进行专门化以更有效地工作):当模板函数需要使用例如sqrt,会的

using std::sqrt;
a=sqrt(b);

这样,对于“常规”类型,它将使用std::sqrt(由using 语句“采用”),而对于您的类型,由于Koenig lookup(顺便说一句,它是您在// Apparently I don't have to specify ns:: 观察到的行为的原因)。

【讨论】:

  • 显然,std::sqrt 符号已经在全局命名空间中(无需“使用 std::sqrt;”)。
  • sqrt 应该在全局命名空间中,前提是您包含 &lt;math.h&gt; 而不是 &lt;cmath&gt;
  • 好吧,我不包括 并且我仍然有一个全局符号 sqrt(进行了独立测试)。它似乎是纯 C sqrt 函数。我不明白为什么会这样,但它让你的答案也很重要
  • @doomyster 这是实际 C++ 实现的常见问题。事实上如此普遍,从 C++11 开始就明确允许实现可以(或不可以)在命名空间 std 之外声明 C 函数,即使对于 cmathcstdio 和它们的兄弟也是如此。
【解决方案2】:

它不安全,因为它不合法(§17.6.4.2.1):

除非另有说明,否则如果 C++ 程序将声明或定义添加到命名空间 std 或命名空间 std 内的命名空间,则其行为未定义。只有当声明依赖于用户定义的类型并且特化满足原始模板的标准库要求并且没有明确禁止时,程序才能将任何标准库模板的模板特化添加到命名空间std

因此,您可以为自己的类型添加专业化。您可以添加重载(或其他任何东西)。

您当前的代码是这样做的正确方法。

【讨论】:

  • 对于像sqrt 这样的东西,添加模拟重载的模板函数是否合法?此模板仅匹配X 类型,因此“几乎”与X 的重载相同。例如。 (在命名空间std 内),执行以下操作:template&lt;typename T&gt; auto sqrt(T x) -&gt; typename std:: enable_if&lt; std:: is_same&lt;T,X&gt;::value, X &gt; :: type { /* do square root and return */; }
  • @Aaron 这是一个超载。你不能添加函数模板(=重载),你只能专门化现有的模板。
  • 正式证明我的担心是正确的。接受答案,因为您引用了标准。谢谢!
【解决方案3】:

在泛型函数中,替代方法是这样做:

template <typename T>
void func(T a)
{
    using std::sqrt;

    /* ... */
    T c = sqrt(a);
    /* ... */
}

并且在标准命名空间中定义额外的东西通常是不安全的(并且不是严格合法的)。

【讨论】:

    【解决方案4】:

    将它放在 std 命名空间中并不是一个好主意。

    由于您有自己的命名空间,您可以将 sqrt 导入您的命名空间并添加专门的 sqrt 函数:

    namespace ns {
      using std::sqrt;
      MyClass sqrt(const MyClass &)
    }
    
    ns::sqrt(...);
    

    【讨论】:

      猜你喜欢
      • 2023-03-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-10-11
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多