MPSolve 为何因 isnan 和 isinf 冲突而无法编译?
结论
include/mps/types.h 定义的函数式宏 isnan(x) 和 isinf(x) 导致了编译失败。编译 C++ 源文件时,预处理器会用这两个宏展开 libc++ <cmath> 中的 std::isnan() 和 std::isinf(),把原本合法的 C++ 代码改成无效语法。
对于 MPSolve 3.2.2,可以先注释或删除 types.h 中的这两组兼容宏,然后重新编译。这样可以消除日志中的四个错误。这是源码级的临时修复,编译通过后仍需测试各条数值计算路径。
为什么宏会破坏 <cmath>
日志中已经给出了宏的展开位置:
.../c++/v1/cmath:568:15: error: expected unqualified-id
568 | return std::isnan(__lcpp_x);
| ^
../../include/mps/types.h:27:3: note: expanded from macro 'isnan'
C/C++ 预处理器不识别命名空间。它只要遇到 isnan(,就会尝试展开同名的函数式宏。因此,下面这段代码:
std::isnan(value)
也会触发 MPSolve 的宏,展开结果大致如下:
std::(sizeof(value) == sizeof(long double) ? ... )
这不是合法的 C++ 表达式,所以编译器会报告 expected unqualified-id。std::isinf() 也会遇到同样的问题。
日志中的以下错误通常是宏展开失败引发的连锁反应:
error: no return statement in constexpr function
it_threshold 未使用和 mps_parse_option_line 的警告不会直接中断这次构建。导致 make 退出的是 <cmath> 中的四个编译错误。
临时修复方法
打开解压后的头文件:
mpsolve-3.2.2/include/mps/types.h
找到 #include <math.h> 后定义 isnan 和 isinf 的部分,注释掉整个兼容代码块。只需修改这一段,不必复制或替换完整的 types.h:
/* Handle systems where isnan and isinf are not available */
#include <math.h>
/*
#ifndef isnan
# define isnan(x) \
(sizeof(x) == sizeof(long double) ? isnan_ld (x) \
: sizeof(x) == sizeof(double) ? isnan_d (x) \
: isnan_f (x))
static inline int isnan_f (float x)
{
return x != x;
}
static inline int isnan_d (double x)
{
return x != x;
}
static inline int isnan_ld (long double x)
{
return x != x;
}
#endif
#ifndef isinf
# define isinf(x) \
(sizeof(x) == sizeof(long double) ? isinf_ld (x) \
: sizeof(x) == sizeof(double) ? isinf_d (x) \
: isinf_f (x))
static inline int isinf_f (float x)
{
return !isnan (x) && isnan (x - x);
}
static inline int isinf_d (double x)
{
return !isnan (x) && isnan (x - x);
}
static inline int isinf_ld (long double x)
{
return !isnan (x) && isnan (x - x);
}
#endif
*/
#include <mps/mt-types.h>
保存后,清理此前生成的目标文件并重新构建:
make clean
make
如果项目使用单独的构建目录,请在原来的构建目录中执行这些命令。修改后若仍出现相同错误,应确认编译器引用的是刚刚修改的 types.h,而非系统中已经安装的另一份 MPSolve 头文件。
更合适的长期处理方式
直接注释这些宏,需要目标平台已经提供可用的 isnan 和 isinf。当前日志来自 macOS libc++,其中的 <cmath> 提供了 std::isnan 和 std::isinf,旧的兼容实现反而会与它们冲突。
如果要维护可移植补丁,可以只在确实缺少这些接口的 C 构建中启用兼容定义,至少应避开 C++:
#ifndef __cplusplus
#ifndef isnan
/* 原有 isnan 兼容实现 */
#endif
#ifndef isinf
/* 原有 isinf 兼容实现 */
#endif
#endif
这只是一种补丁思路。它是否适用于项目支持的所有编译器和平台,还需要结合配置检测和测试套件验证。也不要直接让宏指向 std::isnan 或 std::isinf,因为 types.h 也可能被 C 源文件包含,而 C 语言没有 std 命名空间。
相关冲突见 MPSolve issue #38 和 FreeBSD Bug 273338。如果使用其他版本,应先检查对应源码是否已经修正,以免重复修改。
注意事项
- 这项修改只解决
isnan、isinf与<cmath>的名称冲突。 - 编译成功并不代表数值行为已经经过完整验证。安装后应运行项目自带的测试,并检查实际使用的多项式求根场景。
- 如果目标系统缺少
isnan或isinf,直接删除兼容实现可能产生新的错误,此时仍需保留有条件的回退实现。 - 调整头文件的包含顺序或许能暂时避开
<cmath>内部的错误,但这些宏仍可能破坏后续代码中的std::isnan(),不能作为可靠的修复方案。
备注:内容仅供参考。
