Intel C++ 2024编译器编译CRTP模板枚举代码时出现"invalid arithmetic between different enumeration types"错误求助
各位大佬,我最近在用Intel C++ 2024编译器编译一段基于CRTP(奇异递归模板模式)的自动微分代码时,碰到了一个棘手的错误:invalid arithmetic between different enumeration types。错误指向的是ExprTimes类中定义枚举的这行代码:enum { numNumbers = LHS::numNumbers + RHS::numNumbers };。
这段代码在GCC和Clang下编译完全正常,但Intel编译器就是卡在这里报错,有没有大佬能帮忙分析下原因,给个解决办法?
我的代码如下:
AAD3.h 头文件代码:
#include <memory> #include <string> template <class E> class Expression {}; /************************************************************************************************************************************************************/ // CRTP (Curiously Recurring Template Pattern) template <class LHS, class RHS> class ExprTimes : public Expression<ExprTimes<LHS, RHS>> { LHS lhs; RHS rhs; public: // Constructor explicit ExprTimes (const Expression<LHS>& l, const Expression<RHS>& r) : lhs(static_cast<const LHS&>(l)), rhs(static_cast<const RHS&>(r)) {} double value() const { return lhs.value() * rhs.value(); } enum { numNumbers = LHS::numNumbers + RHS::numNumbers }; // 触发错误的行 std::string writeProgram( // On input, the number of nodes processed so far // On return the total number of nodes processed on exit size_t& processed) { // Process the left sub-DAG const std::string ls = lhs.writeProgram(processed); const size_t ln = - 1; // Process the right sub-DAG const std::string rs = rhs.writeProgram(processed); const size_t rn = processed - 1; // Process this node const std::string thisString = ls + rs + "y" + std::to_string(processed) + " = y" + std::to_string(ln) + " * y" + std::to_string(rn) + "\n"; ++processed; return thisString; } // Input: accumulated adjoint for this node or 1 if top node void pushAdjoint(const double adjoint) { lhs.pushAdjoint(adjoint * rhs.value()); rhs.pushAdjoint(adjoint * lhs.value()); } }; // Operator overload for expressions template <class LHS, class RHS> inline ExprTimes<LHS, RHS> operator*( const Expression<LHS>& lhs, const Expression<RHS>& rhs) { return ExprTimes<LHS, RHS>(lhs, rhs); } /************************************************************************************************************************************************************/ template <class ARG> class ExprLog : public Expression<ExprLog<ARG>> { ARG arg; public: // Constructor explicit ExprLog(const Expression<ARG>& a) : arg(static_cast<const ARG&>(a)) {} double value() const { return log(arg.value()); } enum { numNumbers = ARG::numNumbers }; std::string writeProgram( // On input, the number of nodes processed so far // On return the total number of nodes processed on exit size_t& processed) { // Process the arg sub-DAG const std::string s = arg.writeProgram(processed); const size_t n = processed - 1; // Process this node const std::string thisString = s + "y" + std::to_string(processed) + " = log(y" + std::to_string(n) + ")\n"; ++processed; return thisString; } // Input: accumulated adjoint for this node or 1 if top node void pushAdjoint(const double adjoint) { arg.pushAdjoint(adjoint / arg.value()); } }; // Operator overload for expressions template <class ARG> inline ExprLog<ARG> log(const Expression<ARG>& arg) { return ExprLog<ARG>(arg); } /************************************************************************************************************************************************************/ // Number type, also an expression class Number : public Expression<Number> { double val; std::shared_ptr<double> adj; // Use a shared pointer so that copies of Number hold the same adjoint value (required to make lines 199 and 200 work) public: // Constructor explicit Number(const double v) : val(v), adj(std::make_shared<double>(0.0)) {} // code modified from text to work with shared pointer double value() const { return val; } double adjoint() const { return *adj; // code modified from text to work with shared pointer } enum { numNumbers = 1 }; std::string writeProgram( // On input, the number of nodes processed so far // On return the total number of nodes processed on exit size_t& processed) { const std::string thisString = "y" + std::to_string(processed) + " = " + std::to_string(val) + "\n"; ++processed; return thisString; } void pushAdjoint(const double adjoint) { *adj = adjoint; // code modified from text to work with shared pointer } }; /************************************************************************************************************************************************************/ auto calculate(Number t1, Number t2) { return t1 * log(t2); // 触发错误的调用行 } /************************************************************************************************************************************************************/ template <class E> constexpr auto countNumbersIn(const Expression<E>&) { return E::numNumbers; }
main.cpp 源文件代码:
#include "AAD3.h" int main() { Number x1(2.0), x2(3.0); auto e = calculate(x1, x2); double e_val = e. }
问题分析:
Intel C编译器对枚举类型的类型检查比GCC/Clang更严格。这里LHS::numNumbers和RHS::numNumbers分别属于不同类的匿名枚举类型,虽然它们的底层值都是整数,但C标准实际上不允许不同枚举类型的对象直接进行算术运算——GCC和Clang可能做了隐式转换的宽松处理,但Intel编译器严格遵循了这一点,所以抛出了错误。
解决方案:
方案一:显式转换枚举值为整数类型
修改ExprTimes和ExprLog中的枚举定义,把枚举值转换成整数后再相加:
// 在ExprTimes中 enum { numNumbers = static_cast<int>(LHS::numNumbers) + static_cast<int>(RHS::numNumbers) }; // 在ExprLog中 enum { numNumbers = static_cast<int>(ARG::numNumbers) };
如果想更通用,也可以用std::underlying_type_t来获取枚举的底层类型(需要包含<type_traits>头文件):
#include <type_traits> // ExprTimes中 enum { numNumbers = static_cast<std::underlying_type_t<decltype(LHS::numNumbers)>>(LHS::numNumbers) + static_cast<std::underlying_type_t<decltype(RHS::numNumbers)>>(RHS::numNumbers) }; // ExprLog中 enum { numNumbers = static_cast<std::underlying_type_t<decltype(ARG::numNumbers)>>(ARG::numNumbers) };
方案二:替换枚举为static constexpr int
把所有类中的匿名枚举numNumbers替换成静态常量整数,这样类型完全统一,不会有跨枚举类型运算的问题:
// ExprTimes类中 static constexpr int numNumbers = LHS::numNumbers + RHS::numNumbers; // ExprLog类中 static constexpr int numNumbers = ARG::numNumbers; // Number类中 static constexpr int numNumbers = 1;
同时对应的countNumbersIn函数也要调整返回类型:
template <class E> constexpr int countNumbersIn(const Expression<E>&) { return E::numNumbers; }
总结:
方案二更推荐,因为static constexpr int是C++11及以后标准中更简洁、类型更明确的写法,能更好地兼容各个编译器,也避免了枚举类型带来的潜在类型问题。方案一则适合不想改动太多原有代码的场景,通过显式转换满足Intel编译器的严格检查。
内容来源于stack exchange

