为什么<cmath>中的某些函数不在std命名空间中?

29

我正在开发一个需要使用多种算术类型的项目。因此,我创建了一个头文件,在其中定义了用户自定义算术类型的最小要求:

user_defined_arithmetic.h:

typedef double ArithmeticF;   // The user chooses what type he 
                              // wants to use to represent a real number

namespace arithmetic          // and defines the functions related to that type
{

const ArithmeticF sin(const ArithmeticF& x);
const ArithmeticF cos(const ArithmeticF& x);
const ArithmeticF tan(const ArithmeticF& x);
...
}

令我烦恼的是,当我使用以下代码时:

#include "user_defined_arithmetic.h"

void some_function()
{
    using namespace arithmetic;
    ArithmeticF lala(3);
    sin(lala);
}

我遇到了编译器错误:

error: call of overloaded 'sin(ArithmeticF&)' is ambiguous
candidates are:
double sin(double)
const ArithmeticF arithmetic::sin(const ArithmeticF&)
我从未使用过<math.h>头文件,只使用过<cmath>。我从未在头文件中使用过using namespace std
我正在使用gcc 4.6.*。我检查了含有模糊声明的头文件是什么,结果是:mathcalls.h:
Prototype declarations for math functions; helper file for <math.h>.
...

我知道,<cmath> 包括 <math.h>,但它应该通过std命名空间来保护声明。我深入研究了 <cmath>头文件并发现:

cmath.h :

...

#include <math.h>

...

// Get rid of those macros defined in <math.h> in lieu of real functions.
#undef abs
#undef div
#undef acos
...

namespace std _GLIBCXX_VISIBILITY(default)
{
...
所以命名空间std是在包含之后开始的。这里有什么问题吗,还是我理解错了什么?

4
有些事情您可能需要重新考虑:在使用算术类型(整数类型+双精度浮点型+单精度浮点型)时,传值比传引用通常更高效(也更为常见)。当调用希望使用特定版本的函数时,请限定调用,而不是添加 using namespace X。或者,您可以使用using指令using arithmetic :: sin)。最后,通过编辑“typedef”更改类型的整个方法真的是一个糟糕的想法。 - David Rodríguez - dribeas
@DavidRodriguez-dribeas:谢谢!请问你能给我一个替代方案的提示吗?我正在使用引用传递,因为这个数字可能是自定义类型。这意味着它甚至可以达到几千字节。我希望当我内联函数并在内联中使用std基本函数时,不会有任何问题。或者会有问题吗?你能给我一些建议吗? - Martin Drozdik
@DavidRodriguez-dribeas:我知道C++的方法是声明一个抽象类,但是我使用的矩阵计算库在使用内置类型时会使用重要的优化。我只是不想失去这个优势。 - Martin Drozdik
5
你的实现可以是一个只使用特性的模板(无论是否是类,如果问我,自由函数也可以)。你的算法可以适用于任何算术类型,用户代码可以本地决定使用哪种类型,而不必修改typedef。这意味着用户可以知道他们正在使用的类型,而无需在头文件中搜索typedef。此外,你将能够在单个应用程序中使用多种类型。 - David Rodríguez - dribeas
谢谢!我非常重视你的建议! - Martin Drozdik
1
另请参阅Jonathan Wakely在Red Hat博客上的文章为什么<cstdlib>比您想象的更复杂。Wakely是GCC开发人员之一,负责维护GNU的C++标准库。他特别讨论了数学函数及其可能引起的问题。简而言之,C++具有重载功能,因此有三个abs、三个log等。这些重载分别是intdoublelong double,以适应您的需求。但是C只有每个函数的一个版本。 - jww
3个回答

26

C++标准库的实现允许在全局命名空间和std中声明C语言库函数。有些人会认为这是一个错误,因为(正如你发现的那样)命名空间污染可能会与你自己的名称发生冲突。然而,这就是现实,我们必须接受它。你只需要将名称限定为arithmetic::sin

按照标准的措辞(C++11 17.6.1.2/4):

  

然而,在C++标准库中,除了作为C宏定义的名称外,声明都在命名空间std的命名空间范围内(3.3.6)。未指定这些名称是否首先在全局命名空间范围内声明并通过显式using-declarations(7.3.3)注入到std命名空间中。


1
更糟糕的是,std::exp 有针对浮点数、双精度和整数的重载,而 exp 只适用于双精度。因此,using namespace std; 声明可能会改变结果! - dhardy

3

如果你真的想要,你可以总是围绕cmath写一个小包装器,大致如下:

//stdmath.cpp
#include <cmath>
namespace stdmath
{
    double sin(double x)
    {
        return std::sin(x);
    }
}

//stdmath.hpp
#ifndef STDMATH_HPP
#define STDMATH_HPP
namespace stdmath {
    double sin(double);
}
#endif

//uses_stdmath.cpp
#include <iostream>
#include "stdmath.hpp"

double sin(double x)
{
    return 1.0;
}

int main()
{
    std::cout << stdmath::sin(1) << std::endl;
    std::cout << sin(1) << std::endl;
}

我想这可能会产生一些额外的开销,取决于编译器的聪明程度。


这并不能完全解决问题,考虑 `namespace mylib{ double sin(double x) { return 1.0; } }int main() { using namespace mylib; std::cout << stdmath::sin(1) << std::endl; std::cout << sin(1) << std::endl; }` 你仍然会得到一个“模棱两可的调用错误”。 - alfC
@alfC 不需要。这个答案的重点在于将 <cmath> 包含在 stdmathcpp 文件中,而不是在 hpp 头文件中。 - Ruslan

1
这只是一个试图解决问题的谦虚尝试。(欢迎提出建议。)
我处理这个问题已经很长时间了。一个问题非常明显的案例是这个:
#include<cmath>
#include<iostream>

namespace mylib{
    std::string exp(double x){return "mylib::exp";}
}

int main(){
    std::cout << std::exp(1.) << std::endl; // works
    std::cout << mylib::exp(1.) << std::endl; // works

    using namespace mylib;
    std::cout << exp(1.) << std::endl; //doesn't works!, "ambiguous" call
    return 0;
}

这在我看来是一个很烦人的bug,或者至少是一个非常不幸的情况。(至少在Linux中使用GCC库时,在GCC和clang中如此。)
最近我又试图解决这个问题。通过查看GCC的cmath头文件,似乎该头文件只是用于重载C函数,并在此过程中损坏了命名空间。
namespace std{
   #include<math.h>
}
//instead of #include<cmath>

有了它,这个就可以工作了

using namespace mylib;
std::cout << exp(1.) << std::endl; //now works.

我几乎可以确定这与 #include<cmath> 不完全相同,但大多数函数似乎都能正常工作。
最糟糕的是,最终某些依赖库将会包含 #inclulde<cmath>。至于这一点,我还没有找到解决方案。 注意:不用说,这根本不起作用。
namespace std{
   #include<cmath> // compile errors
}

4
namespace std 中声明任何东西都是未定义行为。 - Ruslan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接